Элегантно обрабатывать отмену задачи
При использовании задач для больших/длительных рабочих нагрузок, которые мне нужно отменить, я часто использую шаблон, подобный этому для действия, выполняемого задачей:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}
Исключение OperationCanceledException не должно регистрироваться как ошибка, но не должно быть проглочено, если задача заключается в переходе в отмененное состояние. Любые другие исключения не должны рассматриваться в рамках этого метода.
Это всегда казалось немного неуклюжим, и визуальная студия по умолчанию разрывает бросок для OperationCanceledException (хотя у меня есть "break on User-unhandled", отключенный теперь для OperationCanceledException из-за моего использования этого шаблона).
В идеале я думаю, что хотел бы сделать что-то вроде этого:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (Exception ex) exclude (OperationCanceledException)
{
Log.Exception(ex);
throw;
}
}
то есть. имеют какой-то список исключений, применяемый к catch, но без поддержки языка, который в настоящее время невозможен (@eric-lippert: С# vNext:)).
Другим способом будет продолжение:
public void StartWork()
{
Task.Factory.StartNew(() => DoWork(cancellationSource.Token), cancellationSource.Token)
.ContinueWith(t => Log.Exception(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
}
public void DoWork(CancellationToken cancelToken)
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
но мне это не очень нравится, так как исключение технически может иметь больше одного внутреннего исключения и у вас не так много контекста при регистрации исключения, как в первом примере (если бы я делал больше, чем просто зарегистрировав его).
Я понимаю, что это немного вопрос стиля, но интересно, есть ли у кого-нибудь лучшие предложения?
Мне просто нужно придерживаться примера 1?
Эамон
Ответы
Ответ 1
Итак, в чем проблема? Просто отбросьте блок catch (OperationCanceledException)
и установите правильные продолжения:
var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
{
var i = 0;
try
{
while (true)
{
Thread.Sleep(1000);
cts.Token.ThrowIfCancellationRequested();
i++;
if (i > 5)
throw new InvalidOperationException();
}
}
catch
{
Console.WriteLine("i = {0}", i);
throw;
}
}, cts.Token);
task.ContinueWith(t =>
Console.WriteLine("{0} with {1}: {2}",
t.Status,
t.Exception.InnerExceptions[0].GetType(),
t.Exception.InnerExceptions[0].Message
),
TaskContinuationOptions.OnlyOnFaulted);
task.ContinueWith(t =>
Console.WriteLine(t.Status),
TaskContinuationOptions.OnlyOnCanceled);
Console.ReadLine();
cts.Cancel();
Console.ReadLine();
TPL отличает отмену и ошибку. Следовательно, аннулирование (т.е. Бросание OperationCancelledException
внутри тела задачи) не является ошибкой.
Главное: не обрабатывать исключения внутри тела задачи, не перебрасывая их.
Ответ 2
Вот как вы элегантно обрабатываете отмену задачи:
Работа с задачами "огонь и забвение"
var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
Task.Run( () => {
cts.Token.ThrowIfCancellationRequested();
// do background work
cts.Token.ThrowIfCancellationRequested();
// more work
}, cts.Token ).ContinueWith( task => {
if ( !task.IsCanceled && task.IsFaulted ) // suppress cancel exception
Logger.Log( task.Exception ); // log others
} );
Обработка ожидания Ожидание завершения задачи/аннулирование
var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
var taskToCancel = Task.Delay( 10000, cts.Token );
// do work
try { await taskToCancel; } // await cancellation
catch ( OperationCanceledException ) {} // suppress cancel exception, re-throw others
Ответ 3
С# 6.0 имеет решение для этого.. Фильтрация исключений
int denom;
try
{
denom = 0;
int x = 5 / denom;
}
// Catch /0 on all days but Saturday
catch (DivideByZeroException xx) when (DateTime.Now.DayOfWeek != DayOfWeek.Saturday)
{
Console.WriteLine(xx);
}
Ответ 4
Согласно этому сообщению в блоге MSDN, вы должны поймать OperationCanceledException
, например
async Task UserSubmitClickAsync(CancellationToken cancellationToken)
{
try
{
await SendResultAsync(cancellationToken);
}
catch (OperationCanceledException) // includes TaskCanceledException
{
MessageBox.Show('Your submission was canceled.');
}
}
Если ваш отменяемый метод находится между другими отменяемыми операциями, вам может потребоваться выполнить очистку при отмене. При этом вы можете использовать вышеуказанный блок catch, но не забудьте правильно перебрасывать его:
async Task SendResultAsync(CancellationToken cancellationToken)
{
try
{
await httpClient.SendAsync(form, cancellationToken);
}
catch (OperationCanceledException)
{
// perform your cleanup
form.Dispose();
// rethrow exception so caller knows youve canceled.
// DONT 'throw ex;' because that stomps on
// the Exception.StackTrace property.
throw;
}
}
Ответ 5
Вы могли бы сделать что-то вроде этого:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException) when (cancelToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}
Ответ 6
Я не совсем уверен, чего вы пытаетесь достичь здесь, но я думаю, что следующий шаблон может помочь
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException) {}
catch (Exception ex)
{
Log.Exception(ex);
}
}
Возможно, вы заметили, что я удалил инструкцию throw отсюда. Это не вызовет исключения, а просто проигнорирует его.
Сообщите мне, если вы намерены сделать что-то еще.
Существует еще один способ, который очень близок к тому, что вы показали в своем коде
catch (Exception ex)
{
if (!ex.GetType().Equals(<Type of Exception you don't want to raise>)
{
Log.Exception(ex);
}
}