.NET4.0で導入されたTask
とCancellationTokenSource
を使用したとき、.NET4.0以前から存在するスレッドの実行をブロックするメソッドを中断できるかどうかを検証する。
動機 (追記)
.NET4.0(VisualStudio2010)の環境下、Task
でマルチスレッドのアプリケーションをプログラムしながら、ふと思った。
「Thread.Interrupt
ならWaitSleepJoin
状態のスレッドを中断できるけど、Task
とCancellationTokenSource
だとこの状態のスレッドは中断できないよね」
Interrupt
には、スレッドをブロックするメソッド(Monitor.Wait
のような)でThreadInterruptedException
を発生させて途中終了させるという機能がある。しかし、Cancel
にはそのような機能はない。
……いや、実は知らないだけで、実は裏でそんな機能があるのではないか。Thread
を捨ててTask
を使おうと喧伝されているのだから、Task
にも似たような機能がついていてもおかしくない。
検証してみよう。そして、ついでだから、ThreadInterruptedException
を発生しないようなメソッドについても調べてみよう。
Monitor.Enter
中のスレッド(Task
)を中断できるか
実験コード
static void Main(string[] args)
{
var cancel = new CancellationTokenSource();
var o = new object();
var task1 = Task.Factory.StartNew(() =>
{
Trace.WriteLine("Task1 - start Monitor.Enter");
Monitor.Enter(o);
Thread.Sleep(10000);
Monitor.Exit(o);
Trace.WriteLine("Task1 - done Monitor.Exit");
});
Thread.Sleep(100);
var task2 = Task.Factory.StartNew(() =>
{
try
{
Trace.WriteLine("Task2 - start Monitor.Enter");
Monitor.Enter(o);
Trace.WriteLine("Task2 - done Monitor.Enter");
Thread.Sleep(1000);
}
catch (Exception ex)
{
Trace.WriteLine(ex.ToString());
throw ex;
}
finally
{
Monitor.Exit(o);
Trace.WriteLine("Task2 - done Monitor.Exit");
}
}, cancel.Token);
task2.ContinueWith(t => Trace.WriteLine(t.Status));
Thread.Sleep(100);
cancel.Cancel();
Trace.WriteLine("Foreground - canceled");
Console.WriteLine("Press Enter Key...");
Console.ReadLine();
}
実行結果
Task1 - start Monitor.Enter
Task2 - start Monitor.Enter
Foreground - canceled
Task1 - done Monitor.Exit
Task2 - done Monitor.Enter
Task2 - done Monitor.Exit
RanToCompletion
結果
中断できない。
Monitor.Enter
中で中断できないのだから、lock
ステートメントでオブジェクトのロック取得待ちの状態についても同様と言える。
その他のスレッドをブロックするメソッドはどうか
下記のメソッドについても同様の実験を行う。
Monitor.Wait
EventWaitHandle.WaitOne
AutoResetEvent.WaitOne
ManualResetEvent.WaitOne
ReaderWriterLock.AcquireWriterLock
Thread.Sleep
実験コード
似たようなコードなので省略
結果
どれも中断できない。
結論
実行をブロックするメソッドのうち、CancellationTokenSource
で中断できるのは、引数にCancellationToken
を指定できるメソッドだけである。
これには、以下のメソッドなどが該当する。
CountdownEvent.Wait
ManualResetEventSlim.Wait
Barrier.SignalAndWait
BlockingCollection.Add,Take
中断できないメソッドの中には、Thread.Interrupt
で中断できたメソッド(Monitor.Wait
)も含まれる。
そのため、Monitor.Wait
を使用したい場合、Thread
を使用する意味が依然として存在するように思える。
外部情報
プログラミングC# 第7版の以下のような記述を裏付けることができた。
既に説明した同期メカニズムの中にも
CancellationToken
を指定できるものがあります(Windowsの同期プリミティブは.NETのキャンセルモデルをサポートしないため、WaitHandle
派生のクラスではサポートされていません。また、Monitor
でもキャンセルはサポートされていません。新規に追加されたAPIではサポートされています)。