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

我正在使用Steven Toub优秀的AsyncPump类 ,它允许控制台应用程序使用async / await关键字。

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

这是我的测试代码:

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

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

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

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

我发布了一个更详细的解释: TAP全局exception处理程序 。

另外,我使用稍微修改过的AsyncPump版本,它确保初始任务开始异步执行(即,在核心循环开始启动之后), TaskScheduler.CurrentTaskScheduler.FromCurrentSynchronizationContext()

 ///  /// PumpingSyncContext, based on AsyncPump /// http://blogs.msdn.com/b/pfxteam/archive/2012/02/02/await-synchronizationcontext-and-console-apps-part-3.aspx ///  class PumpingSyncContext : SynchronizationContext { BlockingCollection _actions; int _pendingOps = 0; public TResult Run(Func> taskFunc, CancellationToken token = default(CancellationToken)) { _actions = new BlockingCollection(); 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; 

在这种情况下,exception将作为AggregateException传播给调用者,而AggregateException.InnerException指向async方法内部的原始exception。

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

您正在观察的是被捕获的顶级exception的行为,而AFAIK严格将其视为VS同步,并且没有办法影响它。 听起来它会成为一个很好的UserVoice项目 。

抛出exception时,您可以选择中断 。