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

标签 c# exception console async-await

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

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

这是我的测试代码:

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 类本身的一部分。这使得调试应用程序变得非常困难。

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

最佳答案

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

async Task 抛出的异常方法不会立即重新抛出。相反,它存储在 Task 中。对象(具有捕获的堆栈上下文),并在通过 task.Result 观察到任务结果时重新抛出, task.Wait() , await tasktask.GetAwaiter().GetResult() .

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

附带说明一下,我使用了稍微修改过的 AsyncPump 版本,这确保初始任务开始异步执行(即,在核心循环开始抽水之后),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 传播给调用者, 与 AggregateException.InnerExceptionasync 内部指向原始异常方法。

关于c# - 如何使用 AsyncPump 在异步控制台应用程序中保留异常上下文?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23161693/

相关文章:

c# - 序列化时忽略 XML 属性

c# - 在运行时计算类代码的哈希值 (C#)?

firebase - 如何在Flutter Firebase App中捕获DatabaseError

javascript - Socket.io隐藏错误信息

c# - ReadLine 吃掉波浪号

c# - 如何使用 C# 在 Chrome 中获取当前的 HTML 选项卡?

c# - 流畅的接口(interface) : Avoid Excessive Parameters On Generic Types

sockets - 10057 通过 Socket SendBuf 时出现 WSA 异常

java - 写入文件异常?

javascript - 检测提示中的无效号码