Способ отмены: отменить отмену и отмену. Новая модель отмены интегрирована в .NET Framework в несколько типов. .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 является совместной. Другими словами, это всегда будет deadlock, потому что ничего не обрабатывает токен отмены, который отменяется, потому что поток задачи заблокирован.
Существует способ обойти это, но он по-прежнему полагается на авторов ненадежного кода, чтобы сделать правильную вещь:
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 намного медленнее, чем монитор (или блокировка).
Действительно, если вы не доверяете автору кода достаточно, чтобы они реализовали совместную отмену, я бы поставил под сомнение здравый смысл того, что они запускаются внутри вашего 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
Спасибо, это полезная информация. Тем не менее, у меня создалось впечатление, что Задача должна прослушивать запрос на аннулирование и вызывать OperationCancelledException самостоятельно, если он не может полностью выполнить. Я попробую ваш код, когда я получу шанс сегодня днем. –
Из первой ссылки «Слушатели могут быть уведомлены об аннулировании запросов путем опроса, регистрации обратного вызова или ожидания ожидающих дескрипторов». Настолько эффективно Task.Wait вызывает прослушивание. –
См. Http://stackoverflow.com/questions/2293976/how-and-if-to-write-a-single-consumer-queue-using-the-task-parallel-library/2779208#2779208 для примера используя IsCancellationRequested для проверки отмены и ответа на него. –