Я мог понять, что существуют различия между кодом / лямбдами, помеченными asyn c, и лямбда-выражениями, не помеченными asyn c, но состояние отличается даже между asyn c lambda и asyn c метод, выполняющий тот же код.
Это не совсем так.
Если вы внимательно посмотрите на это new Task(async () => throw new OperationCanceledException())
, вы увидите, что он вызывает перегрузку new Task(Action action)
(нет перегрузки, которая занимает Func<Task>
). Это означает, что это эквивалентно передаче метода async void
, а не метода async Task
.
Итак:
Task t2 = new Task(async () => throw new OperationCanceledException());
t2.Start();
try { t2.Wait(); } catch { }
Это компилируется в нечто вроде:
private static async void CompilerGeneratedMethod()
{
throw new OperationCanceledException()
}
...
Task t2 = new Task(CompilerGeneratedMethod);
t2.Start();
try { t2.Wait(); } catch { }
Это захватывает поток из ThreadPool и запускает на нем CompilerGeneratedMethod
. Когда исключение вызывается изнутри метода async void
, исключение повторно генерируется где-то уместно (в этом случае оно повторно генерируется в ThreadPool), но сам метод CompilerGeneratedMethod
сразу возвращается. Это приводит к немедленному завершению Task t2
, поэтому его статус RanToCompletion
.
Так что же случилось с исключением? Это собирается закрыть вашу заявку! Наберите Console.ReadLine
в конце вашего Main
и убедитесь, что приложение завершает работу, прежде чем вы сможете нажать клавишу ввода.
Это:
Task t3 = ThrowCancellationException();
Очень разные. Он не пытается ничего запустить на ThreadPool. ThrowCancellationException
выполняется синхронно и синхронно возвращает Task
, который содержит OperationCanceledException
. Task
, содержащий OperationCanceledException
, обрабатывается как Canceled
.
Если вы хотите запустить метод async
в ThreadPool, используйте Task.Run
. Это имеет перегрузку, которая принимает Func<Task>
, что означает, что:
Task t2 = Task.Run(async () => throw new OperationCanceledException());
Компилируется что-то вроде:
private static async Task CompilerGeneratedMethod()
{
throw new OperationCanceledException();
}
...
Task t2 = Task.Run(CompilerGeneratedMethod);
Здесь, когда CompilerGeneratedMethod
выполняется в ThreadPool, он возвращает Task
, содержащий OperationCanceledException
. Затем механизм Task переводит Task t2
в состояние Canceled
.
В качестве исключения, избегайте new Task
и предпочитайте использовать Task.Run
, если вы хотите явно запустить метод на ThreadPool. В TPL есть много методов, которые были введены до async / await и которые путают с ним.