Есть ли простой способ вернуть задачу с исключением?

Я понимаю, что return Task.FromResult(foo) является простым сокращением для:

var tcs = new TaskCompletionSource<TFoo>();
tcs.SetResult(foo);
return tcs.Task;

Есть ли какой-то эквивалент для задачи, которая возвращает состояние исключения?

var tcs = new TaskCompletionSource<TFoo>();
tcs.SetException(new NotSupportedException()); // or whatever is appropriate
return tcs.Task;

Я не вижу ничего подобного Task.FromException. Или было бы более целесообразным просто выбросить исключение, не возвращая задачи?

Ответ 1

Я понимаю, что return Task.FromResult(foo) является простым сокращение для... [ TaskCompletionSource.SetResult].

Собственно, Task.FromResult не использует TaskCompletionSource, его реализация намного проще.

Есть ли какой-то эквивалент для задачи, которая возвращает состояние исключения?

Я считаю, что TaskCompletionSource будет лучшим вариантом для этого. Вы также можете сделать что-то вроде этого:

static Task FromExAsync(Exception ex) 
{
    var task = new Task(() => { throw ex; });
    task.RunSynchronously();
    return task;
}

Исключение не будет распространяться вне возвращаемого task, пока не будет обнаружено через await task или task.Wait(), что должно быть желательным поведением.

Обратите внимание, что если исключение, переданное в FromExAsync, является активным исключением (т.е. уже было выброшено и обнаружено в другом месте), повторное бросание его таким образом потеряет текущую трассировку стека, а информация Watson будет храниться внутри исключения. Существует два способа борьбы с ним:

  • Оберните исключение с помощью AggregateException. Это сделает исходное исключение доступным как AggregateException.InnerException:

static Task FromExAsync(Exception ex) 
{
    var task = new Task(() => { throw new AggregateException(ex); });
    task.RunSynchronously();
    return task;
}
  • Используйте ExceptionDispatchInfo.Capture для потока активного состояния исключения:

static Task FromExAsync(Exception ex) 
{
    var ei = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex);
    var task = new Task(() => { ei.Throw(); });
    task.RunSynchronously();
    return task;
}

Наконец, возможно, самый простой, но худший вариант (из-за служебных данных конечного автомата и предупреждения компилятора) заключается в том, чтобы выкинуть из метода async:

static async Task FromExAsync(Exception ex)
{
    throw ex;
}