У меня есть структура, которая создает CancellationTokenSource, настраивает CancelAfter, затем вызывает асинхронный метод и передает токен. Затем метод async порождает множество задач, передавая токен отмены каждой из них, а затем ожидает сбора задач. Каждая из этих задач содержит логику для корректной отмены путем опроса IsCancellationRequested.
Моя проблема в том, что если я передаю CancellationToken в Task.Run (), возникает исключение AggregateException, содержащее TaskCanceledException. Это предотвращает корректную отмену задач.
Чтобы обойти это, я не могу передать CancelationToken в Task.Run, однако я не уверен, что буду терять. Например, мне нравится идея, что если моя задача зависает и не может выполнить плавную отмену, это исключение приведет к ее отключению. Я думал, что могу связать два токена CancelationToken, чтобы справиться с этим, один «изящный», а другой «принудительный». Однако мне это решение не нравится.
Вот какой-то псудокод, представляющий то, что я описал выше ..
public async Task Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(30000);
await this.Run(cts.Token);
}
public async Task Run(CancellationToken cancelationToken)
{
HashSet<Task> tasks = new HashSet<Task>();
foreach (var work in this.GetWorkNotPictured)
{
// Here is where I could pass the Token,
// however If I do I cannot cancel gracefully
// My dilemma here is by not passing I lose the ability to force
// down the thread (via exception) if
// it's hung for whatever reason
tasks.Add(Task.Run(() => this.DoWork(work, cancelationToken))
}
await Task.WhenAll(tasks);
// Clean up regardless of if we canceled
this.CleanUpAfterWork();
// It is now safe to throw as we have gracefully canceled
cancelationToken.ThrowIfCancellationRequested();
}
public static void DoWork(work, cancelationToken)
{
while (work.IsWorking)
{
if (cancelationToken.IsCancellationRequested)
return // cancel gracefully
work.DoNextWork();
}
}