如何使用AsyncPump在异步控制台应用程序中保留异常上下文?

时间:2014-04-18 20:21:47

标签: c# exception-handling console async-await

我正在使用允许控制台应用程序使用async / await关键字的Steven Toub's excellent AsyncPump class

但是,我遇到一个问题,即代码中抛出的异常会被泵捕获然后重新抛出,这会导致原始调用堆栈和异常上下文丢失。

这是我的测试代码:

class Program
{
  static void Main(string[] arg)
  {
    AsyncPump.Run(() => MainAsync());
  }

  static async Task MainAsync()
  {
    throw new Exception(); // code should break here
  }
}

如果您运行此测试,调试器不会根据需要在throw new Exception()上中断。相反,它会在t.GetAwaiter().GetResult()上中断,这是AsyncPump类本身的一部分。这使调试应用程序非常困难。

有没有办法重新抛出异常,以便调试器在保留调用堆栈和上下文的同时在原始位置中断?

2 个答案:

答案 0 :(得分:2)

GetAwaiter().GetResult()已经正确地重新抛出异常(假设您使用的是.NET 4.5)。调用堆栈已正确保留。

您正在观察的是被捕获的顶级异常的行为,并且AFAIK严格将其视为VS同步,并且无法影响它。听起来它会很好UserVoice item

您可以选择breaking when an exception is thrown

答案 1 :(得分:2)

如果您使用async void的{​​{1}}签名而非MainAsync,则可能会看到所需的行为。这并不意味着您应该更改代码(async Task几乎不是一个好主意),它只是意味着现有行为完全正常

不会立即重新抛出async void方法引发的异常。相反,它存储在async Task对象内(具有捕获的堆栈上下文),并且当通过Tasktask.Result,{task.Wait() {{}} {{}时,将重新抛出任务的结果。 {1}}或await task

我发布了一些更详细的解释:TAP global exception handler

在旁注中,我使用了task.GetAwaiter().GetResult()的略微修改版本,这确保初始任务开始异步执行(即,在核心循环开始抽取之后),AsyncPump为{ {1}}:

TaskScheduler.Current

也可以改变这一部分:

TaskScheduler.FromCurrentSynchronizationContext()

对此:

/// <summary>
/// PumpingSyncContext, based on AsyncPump
/// http://blogs.msdn.com/b/pfxteam/archive/2012/02/02/await-synchronizationcontext-and-console-apps-part-3.aspx
/// </summary>
class PumpingSyncContext : SynchronizationContext
{
    BlockingCollection<Action> _actions;
    int _pendingOps = 0;

    public TResult Run<TResult>(Func<Task<TResult>> taskFunc, CancellationToken token = default(CancellationToken))
    {
        _actions = new BlockingCollection<Action>();
        SynchronizationContext.SetSynchronizationContext(this);
        try
        {
            var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

            var task = Task.Factory.StartNew(
                async () =>
                {
                    OperationStarted();
                    try
                    {
                        return await taskFunc();
                    }
                    finally
                    {
                        OperationCompleted();
                    }
                },
                token, TaskCreationOptions.None, scheduler).Unwrap();

            // pumping loop
            foreach (var action in _actions.GetConsumingEnumerable())
                action();

            return task.GetAwaiter().GetResult();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(null);
        }
    }

    void Complete()
    {
        _actions.CompleteAdding();
    }

    // SynchronizationContext methods
    public override SynchronizationContext CreateCopy()
    {
        return this;
    }

    public override void OperationStarted()
    {
        // called when async void method is invoked 
        Interlocked.Increment(ref _pendingOps);
    }

    public override void OperationCompleted()
    {
        // called when async void method completes 
        if (Interlocked.Decrement(ref _pendingOps) == 0)
            Complete();
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        _actions.Add(() => d(state));
    }

    public override void Send(SendOrPostCallback d, object state)
    {
        throw new NotImplementedException("Send");
    }
}

在这种情况下,异常将作为return task.GetAwaiter().GetResult(); 传播给调用方,return task.Result; 指向AggregateException方法内的原始异常。