未处理的异步异常
Posted
技术标签:
【中文标题】未处理的异步异常【英文标题】:Unhandled async exception 【发布时间】:2015-12-02 01:07:17 【问题描述】:除了避免async void
的原因,是否有可能从调用代码中捕获以下错误?
private static Action ErrorAction
get
return async () =>
await Task.Delay(0);
throw new NotImplementedException();
;
例如在以下两个中我想收集抛出的异常,但是两个测试都失败了:
[Test]
public void SelfContainedExampleTryCatch()
List<Exception> errors = new List<Exception>();
try
ErrorAction();
catch (Exception ex)
errors.Add(ex);
errors.Count().Should().Be(1);
[Test]
public void SelfContainedExampleContinueWith()
List<Exception> errors = new List<Exception>();
var task = Task.Factory.StartNew(ErrorAction);
task.ContinueWith(t =>
errors.Add(t.Exception);
, TaskContinuationOptions.OnlyOnFaulted);
task.Wait();
errors.Count().Should().Be(1);
我知道我可以使用带有async Task
签名的方法;但具体来说,这是我需要处理的 Action 可分配委托异步代码示例,并且在全局级别进行捕获是不切实际的。如果可能的话,同步和异步操作通用的解决方案将是理想的。
希望我错过了一个简单的解决方案,但到目前为止,我只是走入了许多死胡同,并且(可能是错误地)得出结论这是不可能的。任何帮助(或节省时间的浪费)将不胜感激!
【问题讨论】:
测试在什么时候失败? 错误未处理 - 它不会添加到errors
列表中。
@mason 断言 errors.Count().Should().Be(1);
上的测试将失败,因为不会传播异常。
我发现如果我在我的内部处理程序中引入异步代码,我必须使调用代码异步,以及 THAT 的调用代码异步。它最终是异步的。你能解释一下为什么不能使用带有async Task
签名的方法吗?
@AndrewShepherd 它不必一直是异步的。您可以通过 var task = MyCallAsync(); task.Wait();
从未标记为异步的方法中运行异步命令。
【参考方案1】:
在某些情况下,框架要求您使用 async void
,而有时您真的不想使用(例如,ICommand.Execute
)。在这些情况下,我通常建议引入与await
兼容的API,并让您的async void
方法非常简单的包装器:
static Func<Task> ErrorActionAsync
get
return async () =>
await Task.Yield();
throw new NotImplementedException();
;
private static Action ErrorAction
get
return async () => await ErrorActionAsync();
(顺便说一句,我将await Task.Delay(0)
更改为await Task.Yield()
,因为await Task.Delay(0)
是一个noop)。
然后您可以在单元测试中使用ErrorActionAsync
而不是ErrorAction
。这种方法确实意味着您将拥有少量未经测试的代码(async void
包装器 - ErrorAction
)。
但是,如果您想保持ErrorAction
原样,并且可以同步或异步,那么还有另一种可能性。我写了一个AsyncContext
type,你可以这样使用:
[Test]
public void SelfContainedExampleTryCatch()
List<Exception> errors = new List<Exception>();
try
AsyncContext.Run(() => ErrorAction());
catch (Exception ex)
errors.Add(ex);
errors.Count().Should().Be(1);
AsyncContext.Run
将阻塞直到async void
方法完成,并将捕获来自async void
方法的异常并直接引发它们。
【讨论】:
【参考方案2】:如果您希望 ErrorAction 能够满足异步调用,然后仍然能够在调用站点处理异常,那么我认为您只需要硬着头皮将 ErrorAction 更改为返回 Task
。
如果您决定更改合同,最好保持现有非异步实现的同步特性。你可以这样做:
private static Task ErrorAction
get
// Synchronous code here
return Task.FromResult(true);
【讨论】:
【参考方案3】:我认为在不更改合同的情况下返回 Task 是可能的。看这个例子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
namespace ConsoleApplication1
class Program
static void Main(string[] args)
SelfContainedExampleTryCatch();
SelfContainedExampleContinueWith();
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
public static void SelfContainedExampleTryCatch()
var errors = new List<Exception>();
try
ErrorAction();
catch (Exception ex)
errors.Add(ex);
errors.Count().Should().Be(1);
public static void SelfContainedExampleContinueWith()
var errors = new List<Exception>();
var task = Task.Factory.StartNew(ErrorAction);
task.ContinueWith(t =>
errors.Add(t.Exception);
, TaskContinuationOptions.OnlyOnFaulted);
task.Wait();
errors.Count().Should().Be(1);
private static Action ErrorAction
get
return () => DoTask().Wait();
private static async Task DoTask()
await Task.Delay(0);
throw new NotImplementedException();
ContinueWith 测试通过,但其他测试不会。我相信这是你的意图。
【讨论】:
@lonewolf 我相信没有办法解决这个问题。如果您不希望它被阻止,那么您需要更改合同。 问题是我有一个接受动作的公开合同 - 我无法控制第三方传入的内容,因此重新排列输入,除非我可以在接受的动作和调用之间放置一些东西代码不会成功,不过还是感谢您的建议。以上是关于未处理的异步异常的主要内容,如果未能解决你的问题,请参考以下文章
ajax异步提交 有时会出现无bug的数据处理异常-----debug没有问题,正常运行却数据处理不正确,极少机会会出现正常的处理结果