.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.WaitEventWaitHandle.WaitOneAutoResetEvent.WaitOneManualResetEvent.WaitOneReaderWriterLock.AcquireWriterLockThread.Sleep
実験コード
似たようなコードなので省略
結果
どれも中断できない。
結論
実行をブロックするメソッドのうち、CancellationTokenSourceで中断できるのは、引数にCancellationTokenを指定できるメソッドだけである。
これには、以下のメソッドなどが該当する。
CountdownEvent.WaitManualResetEventSlim.WaitBarrier.SignalAndWaitBlockingCollection.Add,Take
中断できないメソッドの中には、Thread.Interruptで中断できたメソッド(Monitor.Wait)も含まれる。
そのため、Monitor.Waitを使用したい場合、Threadを使用する意味が依然として存在するように思える。
外部情報
プログラミングC# 第7版の以下のような記述を裏付けることができた。
既に説明した同期メカニズムの中にも
CancellationTokenを指定できるものがあります(Windowsの同期プリミティブは.NETのキャンセルモデルをサポートしないため、WaitHandle派生のクラスではサポートされていません。また、Monitorでもキャンセルはサポートされていません。新規に追加されたAPIではサポートされています)。