未处理的异步异常

时间:2015-12-02 01:07:17

标签: c# asynchronous async-await

避免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可分配代理异步代码示例,并且在全局级别捕获是不切实际的。如果可能的话,同步和异步动作共同的解决方案将是理想的。

希望有一个我错过的简单解决方案,但到目前为止,我只是走了很多死胡同并且(可能不正确)得出结论它是不可能的。任何帮助(或浪费时间的浪费)将不胜感激!

3 个答案:

答案 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测试通过,但另一个测试没有通过。我认为这是你的意图。