Async Await 和 ContinueWith 未按预期工作
Async Await and ContinueWith not working as expected
我有 the following code 在 .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 的调用时,它总是会调用可以处理 exception/cancellation 和 return 作业结果的延续。我们希望减少创建的异步状态机的数量……但是,情况并非如此。这是一个较小的示例(创建一个新的 .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;
}
throws 方法实际上是在调用时抛出异常,而不是返回错误Task
。没有 Task
供您添加续集;它只是在到达 ContinueWith
调用之前上升调用堆栈。
这里确实没有创建任务。例如,如果你做了一个循环,你将停留在同一个线程和同一个堆栈上。您可以通过在 Throws 方法中执行 Task.FromException 而不是抛出来查看正确的行为。同样在核心 2.1 中,至少您可能会发现异步方法与延续版本一样快,甚至更快,而且分配更少。在尝试优化状态机之前,值得检查您的跟踪编号。此外,如果您抛出异常,您的状态机绝对是您最不关心的性能问题。
我有 the following code 在 .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 的调用时,它总是会调用可以处理 exception/cancellation 和 return 作业结果的延续。我们希望减少创建的异步状态机的数量……但是,情况并非如此。这是一个较小的示例(创建一个新的 .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;
}
throws 方法实际上是在调用时抛出异常,而不是返回错误Task
。没有 Task
供您添加续集;它只是在到达 ContinueWith
调用之前上升调用堆栈。
这里确实没有创建任务。例如,如果你做了一个循环,你将停留在同一个线程和同一个堆栈上。您可以通过在 Throws 方法中执行 Task.FromException 而不是抛出来查看正确的行为。同样在核心 2.1 中,至少您可能会发现异步方法与延续版本一样快,甚至更快,而且分配更少。在尝试优化状态机之前,值得检查您的跟踪编号。此外,如果您抛出异常,您的状态机绝对是您最不关心的性能问题。