我正在使用 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 task
或 task.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.InnerException
从 async
内部指向原始异常方法。
关于c# - 如何使用 AsyncPump 在异步控制台应用程序中保留异常上下文?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23161693/