跨线程封送异常(没有同步上下文)



我有一个同步调用方法的事件处理程序。

void OnSomeEventHappened(int eventInfo)
{
MethodDoingSomething(eventInfo);
}

我想更改此事件处理程序,以便方法调用(对 MethodDoingSomething(变得异步。

问题是 MethodDoingSomething 可能会抛出异常,重要的是,在新的异步行为中,这些异常继续从运行事件处理程序的线程(即用于接收同步情况下异常的原始 thead(引发。

我还需要按照它们到达的相同顺序执行调用。

我的方法是使用阻塞集合来实现 FIFO 任务队列。

void OnSomeEventHappened(int eventInfo)
{
_blockingCollectionFifoQueue.Add(eventInfo);
}

并且有一个调用原始方法的阻塞集合的使用者:

void RunConsumer()
{
foreach (var elem in _blockingCollectionFifoQueue.GetConsumingEnumerable())
{
MethodDoingSomething(elem);
}
}

在这种情况下,我如何获取可能从 MethodDoingSomething 引发的异常,并在主线程中重新抛出它们,就像以前代码同步时一样。

无法像在同步方案中那样在事件处理程序中重新引发异常,因为 - 由于我们现在是异步的 - 我们无法判断哪个方法在该线程上运行,当我们想要抛出时OnSomeEventHappened执行。

但是,您可以在该线程的SynchronizationContext上发布异常,如果有的话。

如果OnSomeEventHappened事件处理程序在 WPF 或 WinForms 应用程序的 UI 线程上运行,则可以使用SychronizationContext

如果它是一个随机线程或线程池线程 - 这意味着该线程上没有SynchronizationContext- 你将不得不编写自己的实现(提示:不要,这是一件很难正确实现的事情(。要在不同的线程上触发某些内容,您需要某种同步或消息传递系统。

如果要始终在同一线程上重新引发异常,可以存储目标线程的同步上下文并使用以下引用:

// Make sure this code is executed on the thread that will receive the exceptions.
// Note that 'Current' will be null for thread pool threads
this.exceptionsSyncContext = SynchronizationContext.Current;

然后,阻止收集处理可能如下所示:

try
{
MethodDoingSomething(elem);
}
catch (Exception ex)
{
// Capture the exception into an ExceptionDispatchInfo so that its 
// stack trace and Watson bucket info will be preserved
var edi = ExceptionDispatchInfo.Capture(ex);
this.exceptionsSyncContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
}

如果要在不同的线程上重新抛出异常,只需将相应的SynchronizationContext引用与eventInfo一起存储在同一集合中即可。 例如,为此使用元组。

async Task RunConsumer()
{
foreach (var elem in _blockingCollectionFifoQueue.GetConsumingEnumerable())
{
try
{
await MethodDoingSomethingAsync(elem);
}
catch (MyException)
{
throw;
}
}
}

这样,来自 MethodDoingSomethingAsync 的异常将在 foreach 循环内的 catch 块中处理。它已经开箱即用。

在调试模式下,您可能会收到用户未处理的异常消息。您需要禁用它或运行程序而不调试才能看到正确的结果。

最新更新