简体   繁体   English

未处理的异步异常

[英]Unhandled async exception

Reasons for avoiding async void aside, is it possible to catch the following error from calling code? 避免async void原因,是否可以从调用代码中捕获以下错误?

private static Action ErrorAction
{
  get
  {
    return async () =>
    {
      await Task.Delay(0);
      throw new NotImplementedException();
    };
  }
}

For example in both of the following I'd like to collect the exceptions thrown, but both tests fail: 例如,在以下两个中我想收集抛出的异常,但两个测试都失败了:

[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);
}

I know I could use a method with an async Task signature; 我知道我可以使用带有async Task签名的方法; but specifically it's the Action assignable delegate async code sample I'm needing to handle, and catching at a global level isn't practicable. 但具体来说,它是我需要处理的Action可分配代理异步代码示例,并且在全局级别捕获是不切实际的。 If it's possible, a solution common for both sync and async Actions would be ideal. 如果可能的话,同步和异步动作共同的解决方案将是理想的。

Hopefully there's a simple solution I've missed, but so far I've only headed down numerous dead ends and have (perhaps incorrectly) concluded it's not possible. 希望有一个我错过的简单解决方案,但到目前为止,我只是走了许多死胡同并且(可能不正确)得出结论它是不可能的。 Any help (or time-wasting saved) would be appreciated! 任何帮助(或浪费时间)将不胜感激!

There are some cases where a framework requires you to use async void when sometimes you'd really rather not (eg, ICommand.Execute ). 在某些情况下,框架要求您使用async void ,有时您真的不愿意(例如, ICommand.Execute )。 In these cases, I generally recommend introducing an await -compatible API and just make your async void methods very simple wrappers: 在这些情况下,我通常建议引入一个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(); }
  }
}

(on a side note, I changed await Task.Delay(0) to await Task.Yield() since await Task.Delay(0) is a noop). (在旁注中,我更改了await Task.Delay(0)await Task.Yield()因为await Task.Delay(0)是noop)。

Then you can use ErrorActionAsync rather than ErrorAction in your unit tests. 然后,您可以在单元测试中使用ErrorActionAsync而不是ErrorAction This approach does mean you'll have small amounts of untested code (the async void wrapper - ErrorAction ). 这种方法确实意味着您将拥有少量未经测试的代码( async void包装器 - ErrorAction )。

However, if you want to keep ErrorAction as-is, and it can be synchronous or asynchronous, then there is another possibility. 但是,如果您希望将ErrorAction保持原样,并且它可以是同步的异步的,那么还有另一种可能性。 I have written an AsyncContext type that you can use like this: 我编写了一个AsyncContext类型 ,您可以像这样使用:

[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 will block until the async void method completes, and will capture exceptions from async void methods and raise them directly. AsyncContext.Run将阻塞,直到async void方法完成,并将从async void方法捕获异常并直接引发它们。

If you want ErrorAction to be able to cater for asynchronous calls and then still be able to handle exceptions at the call site, then I think you will just have to bite the bullet and change the ErrorAction to return Task . 如果你希望ErrorAction能够满足异步调用,然后仍然能够在调用站点处理异常,那么我认为你只需要咬紧牙关并更改ErrorAction以返回Task

If you do decide to change the contract, it will probably be a good idea to maintain the synchronous nature of your existing non-async implmentations. 如果您决定更改合同,那么保持现有非异步实施的同步性可能是个好主意。 You can do this by: 你可以这样做:

private static Task ErrorAction
{
  get
  {
    // Synchronous code here

    return Task.FromResult(true);
  }
}

I think returning Task is possible without changing the contract. 我认为可以在不改变合同的情况下返回任务。 See this example: 看这个例子:

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();
            }
        }
    }

The ContinueWith test passes, but the other test won't. ContinueWith测试通过,但另一个测试不通过。 Which I believe is your intention. 我认为这是你的意图。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM