我怎样才能preserve例外情况下使用AsyncPump异步控制台应用程序? [英] How can I preserve exception context in an async console application using AsyncPump?

查看:186
本文介绍了我怎样才能preserve例外情况下使用AsyncPump异步控制台应用程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的<一个href=\"http://blogs.msdn.com/b/pfxteam/archive/2012/02/02/await-synchronizationcontext-and-console-apps-part-3.aspx\"相对=nofollow>史蒂芬Toub的优秀AsyncPump类允许控制台应用程序使用异步/等待关键字。

I am using Steven Toub's excellent AsyncPump class that allows console applications to use the async/await keywords.

然而,我在哪里被在code抛出异常是由泵捕获,然后重新抛出的问题,这将导致原始调用栈和异常上下文丢失。

However, I have a problem where exceptions that are thrown in the code are caught by the pump and then rethrown, which causes the original call stack and exception context to be lost.

下面是我的测试code:

Here is my test code:

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

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

如果您运行这个测试,调试器不会在打破抛出新的异常()应有的作用。相反,它打破对 t.GetAwaiter()调用getResult(),这是AsyncPump类本身的一部分。这使调试应用程序非常困难。

If you run this test, the debugger doesn't break on the throw new Exception() as desired. Instead, it breaks on t.GetAwaiter().GetResult(), which is part of the AsyncPump class itself. This makes debugging applications very difficult.

有什么办法重新抛出异常,使得在原来的位置上调试器中断,同时preserving调用堆栈和背景?

Is there any way to rethrow exceptions such that the debugger breaks at the original location while preserving the call stack and context?

推荐答案

您可能会看到预期的行为,如果你使用异步无效签名 MainAsync ,而不是异步任务。这并不意味着你应该改变你的code(异步无效几乎从来没有一个好主意),它只是意味着现有行为的完全正常

You would probably see the desired behavior if you used async void signature for MainAsync, rather than async Task. This doesn't mean you should change your code (async void is almost never a good idea), it just means that the existing behavior is perfectly normal.

抛出异步任务方法的一个例外,不会立即重新抛出。相反,它是存储在工作对象内(与捕获堆栈上下文),当该任务的结果得到通过任务观察将被重新抛出。结果 task.Wait()等待任务 task.GetAwaiter()调用getResult()

An exception thrown from async Task methods is not re-thrown immediately. Rather, it is stored inside the Task object (with the captured stack context) and will be re-thrown when the task's result gets observed via task.Result, task.Wait(), await task or task.GetAwaiter().GetResult().

我贴了一下这个的更详细的解释: TAP全局异常处理

I posted a bit more detailed explanation of this: TAP global exception handler.

在一个侧面说明,我用略加修改 AsyncPump ,这可以确保初始任务开始异步执行(即核心循环开始后抽)与 TaskScheduler.Current TaskScheduler.FromCurrentSynchronizationContext()

On a side note, I use a slightly modified version of AsyncPump, which makes sure the initial task starts executing asynchronously (i.e., after the core loop has started pumping), with TaskScheduler.Current being 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");
    }
}

也有可能改变这一部分:

It's also possible to change this part:

return task.GetAwaiter().GetResult();

要这样:

return task.Result;

在这种情况下,异常将被传递到调用者为 AggregateException AggregateException.InnerException 指向最初的异常从异步方法内。

In this case, the exception will be propagated to the caller as AggregateException, with AggregateException.InnerException pointing to the original exception from inside the async method.

这篇关于我怎样才能preserve例外情况下使用AsyncPump异步控制台应用程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆