LoginSignup
11
16

More than 5 years have passed since last update.

[.NET] キャンセル可能なスレッドの休止

Last updated at Posted at 2015-10-06

前置き

Monitor.Wait/PulseよりもManualResetEventSlimを使う は、.NET4.0以降の環境でスレッドをブロックするには、Thread.Interruptで中断できるMonitor.Waitの代わりに、CancellationTokenSourceで中断できるManualResetEventSlimを使うべきだという内容だった。

Thread.Interruptで中断できるメソッドはMonitor.Waitの他にもあって、Thread.SleepThread.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を返す。

よって、キャンセルしたら例外を発生するスレッドの休止は以下のようになる。

CancellableSleep.cs
if (canceltoken.WaitHandle.WaitOne(1000)) // sleep 1000ms
    throw new OperationCanceledException();
CancellableInfiniteSleep.cs
if (canceltoken.WaitHandle.WaitOne()) // Infinite sleep
    throw new OperationCanceledException();

より自然な記述を

canceltoken.WaitHandle.WaitOne(1000)という記述はひいき目に言っても、スリープしているようには見えない。
もっと自然なコードになるように、CancellationTokenのインスタンスにメソッドがあるかのような拡張メソッドを追加することをおすすめする。

CancellationTokenEx.cs
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の拡張クラスであるTaskCanceledExceptionthrowされる

周囲に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

11
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
16