Async Await и ContinueWith не работает должным образом

У меня есть следующий код, который работает на.NET Standard 2.0:

public static Task<jobresult> TryRunAsync(this IJob job,
 CancellationToken cancellationToken = default(CancellationToken))
{
 return job.RunAsync(cancellationToken)
 .ContinueWith(t => {
 if (t.IsFaulted)
 return JobResult.FromException(t.Exception.InnerException);
 if (t.IsCanceled)
 return JobResult.Cancelled;

 return t.Result;
 });
}
</jobresult>

И мы заметили, что он работает не так, как ожидалось. Мы думали, что когда вы ждали вызова TryRun, он всегда вызывал продолжение, которое могло бы обрабатывать исключение/отмену и возвращать результат работы. Мы надеялись уменьшить количество созданных машин с асинхронным доступом... Однако это не тот случай, когда он просто взрывается. Ниже представлен небольшой пример (создайте новое консольное приложение.net core 2.0 и вставьте следующее:

using System;
using System.Threading.Tasks;

namespace ConsoleApp4
{
 public class Program
 {
 public static async Task Main()
 {
 // works
 await DoStuff();
 Console.ReadKey();

 // blows up
 await TryRun();
 Console.ReadKey();
 }

 public static Task DoStuff()
 {
 return Method()
 .ContinueWith(t => Throws())
 .ContinueWith(t => {
 if (t.IsFaulted)
 Console.WriteLine("Faulted");
 else if (t.IsCompletedSuccessfully)
 Console.WriteLine("Success");
 });
 }

 public static Task Method()
 {
 Console.WriteLine("Method");
 return Task.CompletedTask;
 }

 public static Task TryRun()
 {
 return Throws()
 .ContinueWith(t => {
 if (t.IsFaulted)
 Console.WriteLine("Faulted");
 else if (t.IsCompletedSuccessfully)
 Console.WriteLine("Success");
 });
 }

 public static Task Throws()
 {
 Console.WriteLine("Throws");
 throw new ApplicationException("Grr");
 }
 }
}

Вам может понадобиться <langversion>Latest</langversion> В вашем csproj.

ОБНОВИТЬ

В результате мы получили следующий код:

public static Task<jobresult> TryRunAsync(this IJob job, 
 CancellationToken cancellationToken = default(CancellationToken))
{
 var tcs = new TaskCompletionSource<jobresult>(null);
 try {
 var task = job.RunAsync(cancellationToken);
 task.ContinueWith((task2, state2) => {
 var tcs2 = (TaskCompletionSource<object>)state2;
 if (task2.IsCanceled) {
 tcs2.SetResult(JobResult.Cancelled);
 } else if (task2.IsFaulted) {
 tcs2.SetResult(JobResult.FromException(task2.Exception));
 } else {
 tcs2.SetResult(JobResult.Success);
 }
 }, tcs, cancellationToken);
 } catch (Exception ex) {
 tcs.SetResult(JobResult.FromException(ex));
 }

 return tcs.Task;
}
 </object></jobresult></jobresult>
2 ответа

Метод throws на самом деле бросает исключение при вызове, а не возвращает неисправную Task. Там нет Task для вас, чтобы добавить продолжение к; он просто поднимается до стека вызовов, даже доходя до вызова ContinueWith.


На самом деле здесь нет никакой задачи. Например, если вы сделали цикл, вы останетесь в том же потоке и в том же стеке. Вы можете увидеть правильное поведение, выполнив задачу Task.FromException в методе Throws, а не бросая. Также, по крайней мере, в ядре 2.1 вы обнаружите, что метод async будет таким же быстрым или даже быстрее, чем версия продолжения и меньше allocaty. Стоит проверить ваши номера трасс, прежде чем пытаться оптимизировать машину состояния. Кроме того, если вы бросаете исключения, ваш государственный аппарат определенно является наименьшим из ваших проблем.

licensed under cc by-sa 3.0 with attribution.