前置き
Monitor.Wait/PulseよりもManualResetEventSlimを使う は、.NET4.0以降の環境でスレッドをブロックするには、Thread.Interrupt
で中断できるMonitor.Wait
の代わりに、CancellationTokenSource
で中断できるManualResetEventSlim
を使うべきだという内容だった。
Thread.Interrupt
で中断できるメソッドはMonitor.Wait
の他にもあって、Thread.Sleep
とThread.Join
がそれだ。
では、Thread.Sleep
はどうなのか。CancellationTokenSource
でキャンセル可能なスレッドの休止を実現する方法はあるのだろうか。
.NET Framework 4.0
CancellationToken.WaitHandle
CancellationToken
には、WaitHandle
型のWaitHandle
というプロパティが存在する。
WaitHandle
クラスはシグナルを受信することができ、そしてシグナルを受信するまでスレッドをブロックすることで排他制御を実現する。
そして、このWaitHandle
プロパティは、ひもづいているCancellationTokenSource
オブジェクトのCancel
メソッドがコールされたタイミングでシグナルを受信する。
よって、CancellationToken.WaitHandle
がシグナルを受信するのを休止したい時間だけ待ち受けることで、キャンセル可能なスレッドの休止を実現できる。
そのためのWaitHandle
のインスタンスメソッドはWaitOne
だ。
WaitHandle.WaitOne()
WaitHandle.WaitOne(Int32)
WaitHandle.WaitOne(TimeSpan)
待ち受ける時間を指定することもできるし、指定せずにシグナルを受信するまでずっと待ち受けることもできる。
どれを呼び出したとしても、シグナルを受信したらtrue
を、できずにタイムアウトしたならfalse
を返す。
よって、キャンセルしたら例外を発生するスレッドの休止は以下のようになる。
if (canceltoken.WaitHandle.WaitOne(1000)) // sleep 1000ms
throw new OperationCanceledException();
if (canceltoken.WaitHandle.WaitOne()) // Infinite sleep
throw new OperationCanceledException();
より自然な記述を
canceltoken.WaitHandle.WaitOne(1000)
という記述はひいき目に言っても、スリープしているようには見えない。
もっと自然なコードになるように、CancellationToken
のインスタンスにメソッドがあるかのような拡張メソッドを追加することをおすすめする。
public static class CancellationTokenEx
{
public static void CancellableSleep(this CancellationToken self, int millisecond)
{
if (self == null)
throw new ArgumentNullException();
if (self.WaitHandle.WaitOne(millisecond))
throw new OperationCanceledException();
}
}
...
canceltoken.CancellableSleep(1000); // sleep 1000ms
これで、CancellationToken.CancellableSleep(Int32)
とすることで指定ミリ秒だけスリープし、キャンセルされたら例外を発生させることができる。
本当は、Thread.Sleep(Int32, CancellationToken)
とThread
の静的メソッドとして呼び出せるようにしたいのだが、残念ながら、これは許可されていない。
.NET Framework 4.5以降
.NET4.5からは、Task.Delay
という静的メソッドが追加されている。
Task.Delay(Int32, CancellationToken)
Task.Delay(TimeSpan, CancellationToken)
のいずれかを呼び出すことで、キャンセル可能なスレッドの休止を実現できる。
ただし、以下の2点に注意すること。
- 「遅延後に完了するタスクを生成する」という動作をするため、
async
/await
を理解しておく必要がある - キャンセルされると、
OperationCanceledException
の拡張クラスであるTaskCanceledException
がthrow
される
周囲にasync
/await
があるのなら、こちらを使ったほうが自然だ。
しかし、わざわざメソッドにasync
修飾子を追加したり、async
/await
を勉強するぐらいなら、CancellationToken.WaitHandle
を使ったほうがいいだろう。
結論
CancellationTokenSource
を使ってキャンセルされるかもしれない場面でのスレッドの休止には、CancellationToken.WaitHandle
を使う。
.NET4.5以降の環境では、Task.Delay
が選択肢に追加されている。
おまけ
Q Thread.Join
の代替になる、CancellationTokenSource
によってキャンセル可能なTask
の完了の待ち受け方法は?
A Task.Wait(CancellationToken)
参考情報
How to “sleep” until timeout or cancellation is requested in C#4.0
http://stackoverflow.com/questions/18715099/how-to-sleep-until-timeout-or-cancellation-is-requested-in-c4-0