これを行う方法は、CancellationToken と新しいキャンセル モデルを使用することです。新しいキャンセル モデルは、いくつかの種類で .NET Framework に統合されています。最も重要なものは、System.Threading.Tasks、System.Threading.Tasks.Task、System.Threading.Tasks.Task、および System.Linq.ParallelEnumerable です。
これがあなたの問題の例です。呼び出し元のコードが最初にロックを取得し、次にデッドロックされたタスクが同じロックを取得しようとするため、このコードは常にデッドロックします。
public void Example()
{
object sync = new Object();
lock (sync)
{
CancellationTokenSource canceller = new CancellationTokenSource();
ManualResetEvent started = new ManualResetEvent(false);
Task deadlocked = Task.Factory.StartNew(() =>
{
started.Set();
// EVIL CODE: This will ALWAYS deadlock
lock(sync) { };
},
canceller.Token);
// Make sure task has started.
started.WaitOne();
canceller.Cancel();
try
{
// Wait for task to cancel.
deadlocked.Wait();
}
catch (AggregateException ex)
{
// Ignore canceled exception. SIMPLIFIED!
if (!(ex.InnerException is TaskCanceledException))
throw;
}
}
}
TPL でのタスクのキャンセルは協調的です。つまり、タスク スレッドがロックされているためにキャンセルに設定されているキャンセル トークンが処理されないため、これは常にデッドロックになります。
これを回避する方法はありますが、信頼できないコードの作成者が正しいことを行うことに依存しています。
public static void Example2()
{
Mutex sync = new Mutex(true);
CancellationTokenSource canceller = new CancellationTokenSource();
bool started = false;
Task deadlocked = Task.Factory.StartNew(() =>
{
started = true;
// EVIL CODE: This will ALWAYS deadlock
WaitHandle.WaitAny(new WaitHandle[] { canceller.Token.WaitHandle, sync });
},
canceller.Token);
// Make sure task has started.
while (!started) { }
canceller.Cancel();
try
{
// Wait for task to cancel.
deadlocked.Wait();
}
catch (AggregateException ex)
{
// Ignore canceled exception. SIMPLIFIED!
if (!(ex.InnerException is TaskCanceledException))
throw;
}
}
注意点; キャンセルは協力的です。Token.WaitHandle を使用してハンドルを取得し、他の同期プリミティブのハンドルと共に待機できます。Mutex は、Monitor (またはロック) よりもはるかに低速です。
実際、コードの作成者に協調的なキャンセルを実装させるほど信頼していない場合は、同じスレッドの AppDomain 内でそれらを実行することの正気を疑うでしょう。
詳細については、以下を参照してください。
http://msdn.microsoft.com/en-us/library/dd997364.aspx
http://msdn.microsoft.com/en-us/library/dd537607.aspx
http://msdn.microsoft.com/en-us/library/ee191552.aspx