Почему задача не отменяется при вызове метода CancellationTokenSource в асинхронном методе?
Я создал небольшую обертку вокруг CancellationToken
а также CancellationTokenSource
, У меня проблема в том, что CancelAsync
метод CancellationHelper
не работает, как ожидалось.
Я испытываю проблему с ItShouldThrowAExceptionButStallsInstead
метод. Чтобы отменить запущенную задачу, она вызывает await coordinator.CancelAsync();
, но на самом деле задача не отменена и не выдает исключение на task.Wait
ItWorksWellAndThrowsException
кажется, работает хорошо, и он использует coordinator.Cancel
, который не является асинхронным методом вообще.
Вопрос, почему это задание не отменяется, когда я звоню CancellationTokenSource
Отменить метод в асинхронном методе?
Не позволяйте waitHandle
сбить вас с толку, это только из-за того, что вы не даете заданию закончиться раньше.
Пусть код говорит сам за себя:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace TestCancellation
{
class Program
{
static void Main(string[] args)
{
ItWorksWellAndThrowsException();
//ItShouldThrowAExceptionButStallsInstead();
}
private static void ItShouldThrowAExceptionButStallsInstead()
{
Task.Run(async () =>
{
var coordinator = new CancellationHelper();
var waitHandle = new ManualResetEvent(false);
var task = Task.Run(() =>
{
waitHandle.WaitOne();
//this works well though - it throws
//coordinator.ThrowIfCancellationRequested();
}, coordinator.Token);
await coordinator.CancelAsync();
//waitHandle.Set(); -- with or without this it will throw
task.Wait();
}).Wait();
}
private static void ItWorksWellAndThrowsException()
{
Task.Run(() =>
{
var coordinator = new CancellationHelper();
var waitHandle = new ManualResetEvent(false);
var task = Task.Run(() => { waitHandle.WaitOne(); }, coordinator.Token);
coordinator.Cancel();
task.Wait();
}).Wait();
}
}
public class CancellationHelper
{
private CancellationTokenSource cancellationTokenSource;
private readonly List<Task> tasksToAwait;
public CancellationHelper()
{
cancellationTokenSource = new CancellationTokenSource();
tasksToAwait = new List<Task>();
}
public CancellationToken Token
{
get { return cancellationTokenSource.Token; }
}
public void AwaitOnCancellation(Task task)
{
if (task == null) return;
tasksToAwait.Add(task);
}
public void Reset()
{
tasksToAwait.Clear();
cancellationTokenSource = new CancellationTokenSource();
}
public void ThrowIfCancellationRequested()
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
}
public void Cancel()
{
cancellationTokenSource.Cancel();
Task.WaitAll(tasksToAwait.ToArray());
}
public async Task CancelAsync()
{
cancellationTokenSource.Cancel();
try
{
await Task.WhenAll(tasksToAwait.ToArray());
}
catch (AggregateException ex)
{
ex.Handle(p => p is OperationCanceledException);
}
}
}
}
1 ответ
Отмена в.Net является кооперативной.
Это означает, что тот, кто держит CancellationTokenSource
сигналы отмены и тот, который держит CancellationToken
необходимо проверить, был ли отменен сигнал (либо путем опроса CancellationToken
или путем регистрации делегата для выполнения, когда он сигнализируется).
В вашем Task.Run
вы используете CancellationToken
в качестве параметра, но вы не проверяете его внутри самой задачи, поэтому задача будет отменена только в том случае, если токен был сигнализирован до того, как задача имела возможность начать.
Чтобы отменить задачу во время ее выполнения, необходимо проверить CancellationToken
:
var task = Task.Run(() =>
{
token.ThrowIfCancellationRequested();
}, token);
В вашем случае вы блокируете на ManualResetEvent
так что вы не сможете проверить CancellationToken
, Вы можете зарегистрировать делегата на CancellationToken
это освобождает событие сброса:
token.Register(() => waitHandle.Set())