TIPS:非同期:awaitを含むコードをロックするには?(SemaphoreSlim編)[C#、VB]lock/SyncLockステートメントの代わりにSemaphoreSlimクラスを使い、await/Await演算子を含むコードで排他ロックを行う方法を解説する。4.5以降async修飾子/await演算子(VBではAsync修飾子/Await演算子、以降は「async/await」と略す)によって、非同期プログラミングは簡潔に記述できるようになった。ところが、複数スレッド間の排他ロックを実現するために使ってきたlockステートメント(VBではSyncLockステートメント)が、await演算子(VBではAwait演算子、以降は省略)を含むコードでは使えなくなってしまったのである。Phoneアプリ)を書いていて困った経験を持つ人も多いだろう。本稿では、SemaphoreSlimクラス(System.Threading名前空間)を使う方法を説明する。従来のlock/SyncLockステートメントによる排他ロック従来は、lock/SyncLockステートメントを使ってスレッド間の排他ロックを簡潔に記述していた。その方法を確認しておこう。次のコードは、Taskクラス(System.Threading.Tasks名前空間)を利用した非同期処理の例だ。排他ロックしていないため、2つの処理が同時に実行される。ModuleTaskクラスを使って2つの非同期処理を実行するコード例(上:C#、下:VB)LongTimeMethod1メソッドは、ほぼ同時に(Mainメソッド内で指定した約100ミリ秒だけずれて)処理を開始し、非同期に実行される。C#ではSystem名前空間とSystem.Threading.Tasks名前空間をインポートしておく必要がある(以下同じ)。これを実行してみると、次の画像のようになる。LongTimeMethod1メソッドが2つ同時に実行されている(=非同期処理)。非同期処理をするのが目的のコードなので、この結果は想定通りである。ところで、非同期処理の途中で共通のリソースにアクセスしなければならないことがある。非同期に実行されている複数の処理から同時に1つのリソースにアクセスできない(実行してしまうと何が起きるか分からない)ので、その部分は一度に1つの処理だけが行われるようにしたい。それがスレッド間の排他ロックだ。これは、次のコードのようにlock/SyncLockステートメントを使って記述できる。Sublock/SyncLockステートメントを使って排他ロックするコード例(上:C#、下:VB)改修したLongTimeMethod1メソッドのみを示す(Mainメソッドは前と同じ)。前のコードに対して、太字の部分を追加した。一般的には、非同期処理の途中で必要最小限の範囲だけをロックする。ここではサンプルということで、メソッドの中身を全部ロックしている。これで次の画像のように、2つの非同期処理が排他的に動くようになる。上記と同様なコードをasync/awaitを使って書いてみよう。呼び出される側を、async/awaitを使って非同期に動作するメソッドにする(次のコード)。Moduleasync/awaitを使ったメソッドを使って2つの非同期処理を実行するコード例(上:C#、下:VB)ここで注目しておいてほしいのは、await演算子以降のコードが以前のスレッドとは異なるスレッドで実行される可能性がある、という点だ。これを実行してみると、次の画像のように2つの処理がやはり非同期実行される。async/awaitを使ったメソッドを使って2つの非同期処理を実行した例では、また先ほどと同様に、lock/SyncLockステートメントを使って排他ロックするコードを書いてみよう(次のコード)。Functionasync/awaitを使ったメソッドでlock/SyncLockステートメントを使って排他ロックするコード例(上:C#、下:VB)先ほどと同様にlock/SyncLockステートメントを追加した。lock/SyncLockステートメントがビルドエラーになってしまう。ところが「lock/SyncLockステートメント内ではawait演算子を使えない」というビルドエラーになってしまう。?lock/SyncLockステートメントは、そのスコープに入るときにロックを獲得し、スコープから抜けるときにロックを解放する。ロックを解放するには条件があって、ロックを獲得したときと同じスレッドで解放しなければならない(「スレッドアフィニティがある」という)。コード中にコメントしたように、await演算子以降は違うスレッドで実行される可能性がある(すなわち、違うスレッドでロックを解放する可能性がある)ために、コンパイラーがビルドエラーとしているのだ。このようにスレッドアフィニティのあるロック機構は、lock/SyncLockステートメント(System.Threading名前空間のMonitorクラスを内部的に使用)の他に、MutexクラスやReaderWriterLockSlimクラス(ともにSystem.Threading名前空間)などがある。スレッドアフィニティのあるロック機構は、async/awaitを使っているコードでは利用できない。await演算子を含むコードをロックするには?上述したlock/SyncLockステートメントが使えない理由が分かれば、対処法も理解できるだろう。スレッドアフィニティのないロック機構を利用すればよいのである。スレッドアフィニティのないロック機構としてSemaphoreSlimクラス(System.Threading名前空間)がある。これを使って排他ロックするコードは次のようになる。FunctionSemaphoreSlimクラスを使って排他ロックするコード例(上:C#、下:VB)SemaphoreSlimクラスはスレッドアフィニティがないので、ロック中にスレッドが変わっても問題ない。これで、await演算子を含むコードでも排他ロックできた(次の画像)。メールマガジンをご購読ください。Japanスマートジャパン3Dモデラボキャリア@IT自分戦略研究所エンジニアライフディルバートアプリITmediaねとらぼねこらぼついトピ!ITmediaはアイティメディア株式会社の登録商標です。