在 System.Threading.Timers TimerCallback 事件处理程序中捕获异常然后重新抛出未发送回主线程

Exception caught in System.Threading.Timers TimerCallback event handler and then re-thrown not sent back to main thread

我需要将定时事件中抛出的异常冒泡并在事件处理程序上下文之外进行处理。我读过 System.Threading.Timers 可以做到这一点,前提是,如 的答案中所述,异常在回调方法中被捕获并使用一种机制重新抛出它。使用此示例作为指南,我创建了一个抛出的事件处理程序和一个应该使用 IProgress 对象捕获并重新抛出异常的方法:

void ThrowerThreaded() {
    var progress = new Progress<Exception>((ex) => {
        throw ex;
    });
    Timer timer = new Timer(x => onTimerElapsedThrow2(progress), null, 10, -1);
    Thread.Sleep(1000);
}

void onTimerElapsedThrow2(IProgress<Exception> progress) {
    try {
        throw new Exception();
    } catch (Exception ex) {
        progress.Report(ex);
    }
}

然后我写了一个单元测试看异常是否会冒泡:

[TestMethod]
public void TestThreadedTimerThrows() {
    Assert.ThrowsException<Exception>(ThrowerThreaded);
}

测试用例失败表明没有抛出异常。如果我调试测试用例,我可以清楚地看到异常被捕获并在 ThrowerThreaded() 中重新抛出,但是该方法仍然继续并正常存在。为什么异常仍然被抑制?

我猜 ThrowerThreaded 是 运行 在后台线程上。这意味着它没有 synchronizationContext,因为它们旨在同步 UI 应用程序。这意味着在线程池上调用了回调:

Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed. If there is no current SynchronizationContext at the time of construction, the callbacks will be invoked on the ThreadPool.

在线程池线程上重新抛出异常可能会终止该线程,我有点惊讶它没有终止应用程序,但您正在使用的测试框架可能会覆盖这种行为。

要解决这个问题,您确实需要 处理 回调中的异常而不是 re-throwing 它。如果您不处理异常,谁来处理?有一个 unhandledExceptionEvent,但它用于在您关闭应用程序之前记录日志。

您可以在 ThrowerThreaded 中通过回调来处理异常,您将异常处理委托给了它。另一种选择是创建一个 TaskCompletionSource 允许您 return 任务,并通过在源上调用 SetException 将任务设置为 'failed'。

re-throw 同一个异常对象也是不好的做法,因为您将丢失调用堆栈,您应该将异常包装在抛出的新异常中。