コンカレントプログラミングの本を読んでいると、必ずデッドロックの話がでてくるので、実際にデッドロックするのか試したくなった。
デッドロックするコード
DeadLock() メソッドをシングルスレッドしか許されていない箇所から呼び出すとデッドロックが起こる。例えばWPFのUIスレッドから、これを呼び出すとデッドロックが起こる。理由は task.Wait() を呼び出すとこのスレッドのところで、待ちが発生する。一方、WaitAsAsync() メソッドの方では、await で、Task.Delay() を呼んでいる。await のセクションに入るときに現在のスレッドのコンテキストを保存する。await の箇所が終わると、そのコンテキストをリストアするのだが、その時にそのコンテキストは、task.Wait() によってロックがかかっているために、デッドロックになる。
private async Task WaitAsAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
}
private void DeadLock()
{
Task task = WaitAsAsync();
task.Wait();
}
WPF アプリ
WPFは触ったことないけど、師匠のブログを読んで簡単なアプリを作ってみた。ボタンをおしていくと5回目で、Thread.Sleep() がかかり、10回目でこのデッドロックのロジックが呼ばれる。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Prefecture();
}
private async Task WaitAsAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
}
private void DeadLock()
{
Task task = WaitAsAsync();
task.Wait();
}
private int count = 0;
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = (Button) sender;
button.Content = string.Format($"{++count} times.");
if (count == 4)
{
button.Content = string.Format($"Sleep 10 sec....");
}
if (count == 4)
{
Thread.Sleep(TimeSpan.FromSeconds(10));
}
if (count == 10)
{
button.Content = string.Format($"DeadLock....");
}
if (count == 11)
{
DeadLock();
}
}
}
<Window x:Class="WPFSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition>
</RowDefinition>
<RowDefinition>
</RowDefinition>
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Path=Data}" x:Name="comboBox" Grid.Row ="0" Grid.Column="0"/>
<Button Content="0回" Grid.Row="1" Grid.Column="0" Click="Button_Click"/>
</Grid>
</Window>
デッドロックに行く直前なら、プルダウンなどの操作できるが、一旦行ってしまうと何もできなくなる。
操作不能
回避方法
ConfigureAwait() を使うと、コンテキストのリストアをするか否かを制御できる。falseにすると、コンテキストをリストアして元のスレッドに戻ろうとしない(違うスレッドを使う)ので、デッドロックが起こらなくなる。
private async Task WaitAsAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}