一位读者,许多作家

时间:2009-11-04 01:36:35

标签: c# .net multithreading exception queueuserworkitem

相关:How to catch exceptions from a 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:我宁愿不接受读卡器线程上的锁,因为它位于热门路径上,而且我会非常经常地使用并释放锁。

3 个答案:

答案 0 :(得分:3)

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

try
{
    ... stuff happens here...
}
catch (Exception ex1)
{
    lock(queue)
    {
        queue.Enqueue(ex1);
        Monitor.PulseAll(queue);
    }
}

并处理它:


while(!stopped)
    lock (queue)
    {
        while (queue.Count > 0)
            processException(queue.Dequeue());
        Monitor.Wait(queue);
    }

答案 1 :(得分:2)

读取和写入引用都是原子的(参见C# Spec),我几乎可以肯定锁确实会造成内存障碍,所以你正在做的是可能安全。

但实际上只是在阅读时使用锁定。它保证工作;如果你们每次看到它都没有被锁定,你就知道出现了问题,如果锁定导致你出现性能问题,那么你就会经常检查标记方式,而这只是“正确的事情。”

答案 2 :(得分:1)

即使您可能只关心第一个例外,您可能仍然希望使用锁定至少有两个原因:

  1. 在多核CPU中,如果没有变量volatile(或执行任何内存屏障操作),可能会有一个时刻,在不同核心上运行的线程可能会看到不同的值。 (我不确定在工作线程中调用lock(queue)会导致任何内存屏障操作。) (更新)在工作程序中调用lock(queue)线程将导致内存屏障操作,如Eric在下面的评论中所指出的那样。
  2. <击> 2。请记住References are not addresses(由Eric Lippert提供)(如果您假设参考是32位CLR中的32位地址,可以原子读取)。引用的实现可以更改为一些不透明的结构,这些结构在将来的CLR版本中可能无法原子读取(尽管我认为在可预见的将来不太可能发生:))并且您的代码将会中断。