如何使用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.Result
, task.Wait()
, await task
或task.GetAwaiter().GetResult()
通过任务的结果)进行观察时重新抛出task.GetAwaiter().GetResult()
。
我发布了一个更详细的解释: TAP全局exception处理程序 。
另外,我使用稍微修改过的AsyncPump
版本,它确保初始任务开始异步执行(即,在核心循环开始启动之后), TaskScheduler.Current
为TaskScheduler.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时,您可以选择中断 。