2

我有以下在 .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;
         });
}

我们注意到它没有按预期运行。我们认为,当您等待调用 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;
}
4

2 回答 2

6

throws 方法实际上是在调用时抛出异常,而不是返回 faulted Task。您无需Task添加延续;它只是在到达调用之前就向上调用堆栈ContinueWith

于 2017-12-18T14:48:54.823 回答
0

实际上这里没有创建任务。例如,如果你做了一个循环,你会留在同一个线程和同一个堆栈上。您可以通过在 Throws 方法中执行 Task.FromException 而不是抛出来查看正确的行为。同样在核心 2.1 中,至少您可能会发现异步方法与延续版本一样快甚至更快,并且分配更少。在尝试优化状态机之前,值得检查您的跟踪号。此外,如果您抛出异常,您的状态机绝对是您最不关心的性能问题。

于 2017-12-18T17:37:15.080 回答