避免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可分配代理异步代码示例,并且在全局级别捕获是不切实际的。如果可能的话,同步和异步动作共同的解决方案将是理想的。
希望有一个我错过的简单解决方案,但到目前为止,我只是走了很多死胡同并且(可能不正确)得出结论它是不可能的。任何帮助(或浪费时间的浪费)将不胜感激!
答案 0 :(得分:2)
在某些情况下,框架要求您使用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
方法中的异常并直接引发它们。
答案 1 :(得分:0)
如果你希望ErrorAction能够满足异步调用,然后仍然能够在调用站点处理异常,那么我认为你只需要咬紧牙关并更改ErrorAction以返回Task
如果您决定更改合同,那么保持现有非异步实施的同步性可能是个好主意。您可以通过以下方式执行此操作:
private static Task ErrorAction
{
get
{
// Synchronous code here
return Task.FromResult(true);
}
}
答案 2 :(得分:-2)
我认为在不改变合同的情况下返回任务是可能的。见这个例子:
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测试通过,但另一个测试没有通过。我认为这是你的意图。