きな粉もち.net

.NET関連仕事に携わっています。OSSのソースを読んで気がついたことを中心に呟いたりブログに投稿したりしています。最近はUiPathを使ったRPAも研究中。気軽にフォローやツッコミよろしくおねがいします! Gitはここを使っています https://github.com/kinakomotitti

基礎学習 × .NET Framework の非同期処理を見直してみた × その2

この記事の目的

この記事では、
.【NET Framework】非同期処理のキャンセル処理についてまとめること
を目的としています。

本題

★はじめに

ここでは、「プログラミング.NET Framework 第4版」を読んで学んだことを中心にアウトプットします。

「その2」ということで、今回は、Threadクラス、ThreadPoolクラス、Taskクラスを使った非同期処理のキャンセルの実装方法とそれぞれの注意点をまとめてみます。

.NET Frameworkの非同期処理概要・キャンセル編

教科書にはこうありました。

    • -

.NET Frameworkはキャンセル処理の標準的なパターンを提供しています。
このパターンは協調的(cooperative)です。

キャンセルしたい処理を実行するコードと、
処理をキャンセルしようとするコードは、
両方ともこれから説明する型を使用しなければなりません。

    • -


基本的に、.NET Frameworkで非同期処理をキャンセルするためには、
System.Threading.CancellationTokenSource
を使った実装をする必要があります。

ということで、ここではNET Framework 4 以降で導入された、
「キャンセルのための統一されたモデル」にだけ絞って実装を整理していきます。

★Threadクラス

Threadクラスについても、CancellationTokenを使った協調的なキャンセル処理を実装することができます。

試しに、Static変数を使って、キャンセルされたことを非同期処理を実行しているスレッドに通知したり(以下のコードの「パターン1;その1」)、Abortメソッドを使い、スレッドそのものを停止させる(以下のコードの「パターン1;その2」)という処理の実装もしてみましたが、特に意味はなさそうです(´▽`)

非同期処理を実行するメソッドにCancellationTokenを渡し、
非同期処理を実行するメソッド内の処理で、CancellationTokenのIsCancellationRequestedプロパティにアクセスすることで、処理がキャンセルされたことを知ることができます(以下のコードの「パターン2」)


★ThreadPoolクラス

ThreadPoolクラスでも、Threadクラスと同じように、協調的なキャンセルのパターンが利用することができます。

当然ですが、キャンセル処理を想定していないメソッドはキャンセルすることができません(以下のコードの「キャンセルできない」とコメントがあるところ)


★Taskクラス

Taskクラスでも、Thread/ThreadPoolクラスと同じように、CancellationTokenを利用して協調的なキャンセル処理を実装することができます。

TaskとThread/ThreadPoolの違いは、Taskには以下のステータスの違いがある点です。
・完了:処理をすべて実行し、処理が正常に終わったことを示す
・失敗:処理が何らかの理由で失敗したことを示す
・キャンセル:処理をすべて実行できなった(キャンセルされた)ことを示す

そのため、Taskでは、これらのステータスを知るため、ThrowIfCancellationRequestedメソッドを利用します。
ThrowIfCancellationRequestedを呼び出すと、System.Threading.Tasks.TaskCanceledExceptionがスローされます。
この例外は、TaskのResultプロパティにアクセスしたとき、System.AggregateException のInnerExceptionsリストに格納されてリスローされます。





その2、まとめ

よくよく調べてみると、.NET Framework4以降に追加されたCancellation Tokenによる協調的なキャンセルモデルにより、Taskも、Threadも、ThreadPoolも、同じような実装で処理をキャンセルすることができました。

ということで、今回のまとめは以下の通りです。

  • 非同期処理のキャンセルはCancellationTokenを利用する
  • Thread/ThtreadPoolを利用するときは、IsCancellationRequested。
  • Taskを利用するときは、IsCancellationRequestedで判断したのち、ThrowIfCancellationRequestedで例外をスローする。