1

相关:如何从 ThreadPool.QueueUserWorkItem 中捕获异常?

我在由ThreadPool.QueueUserWorkItem()启动的后台线程中捕获异常,并通过共享实例变量将它们传播到主线程。

后台线程这样做:

try
{
    ... stuff happens here...
}
catch (Exception ex1)
{
    lock(eLock) 
    {
        // record only the first exception
        if (_pendingException == null) 
            _pendingException = ex1;
    }
}

_pendingException 有多个潜在的编写者——多个后台线程——所以我用锁保护它。

在主线程中,我必须在阅读之前获得锁_pendingException吗?或者我可以简单地这样做:

if (_pendingException != null)
    ThrowOrHandle(); 

编辑:
ps:我宁愿不锁定读者线程,因为它在热路径上,我会非常非常频繁地获取和释放锁定。

4

3 回答 3

3

你将无法轻易逃脱。如果另一个线程在读者处理现有异常之前抛出异常,您将丢失异常。你需要的是一个同步队列:

尝试
{
    ......这里发生了一些事情......
}
捕获(异常 ex1)
{
    锁(队列)
    {
        queue.Enqueue(ex1);
        Monitor.PulseAll(队列);
    }
}

并处理它:

而(!停止)
    锁(队列)
    {
        while (queue.Count > 0)
            processException(queue.Dequeue());
        Monitor.Wait(队列);
    }
于 2009-11-04T01:44:36.573 回答
2

对引用的读取和写入是原子的(请参阅C# Spec),我几乎可以肯定锁确实会创建内存屏障,所以是的,您正在做的事情可能是安全的。

但实际上只是在你的阅读周围使用锁。保证可以工作;如果您每个人都看到它不是在锁中访问的,那么您就知道出了点问题,如果锁导致您出现性能问题,那么您检查标志的方式太频繁了,这只是“正确的做法”。

于 2009-11-05T04:30:13.517 回答
1

即使您可能只关心第一个异常,您可能仍然希望使用 lock 至少有两个原因:

  1. 在多核 CPU 中,如果没有使变量 volatile(或执行任何内存屏障操作),则可能有一段时间运行在不同内核上的线程可能会看到不同的值。(我不确定调用lock(queue)工作线程会导致任何内存屏障操作)。 (更新)调用lock(queue)工作线程将导致内存屏障操作,正如 Eric 在下面的评论中指出的那样。

2. 请记住,引用不是地址(由 Eric Lippert 撰写)(如果您假设引用是 32 位 CLR 中可以原子读取的 32 位地址)。引用的实现可以更改为一些不透明的结构,这些结构在未来的 CLR 版本中可能无法自动读取(尽管我认为在可预见的未来不太可能发生 :)),您的代码将会中断。

于 2009-11-04T23:18:04.923 回答