One reader, many writers
Related: How to catch exceptions from a ThreadPool.QueueUserWorkItem?
I am catching e开发者_如何转开发xceptions in background threads started by ThreadPool.QueueUserWorkItem(), and propagating them to the main thread via a shared instance variable.
The background threads do this:
try
{
... stuff happens here...
}
catch (Exception ex1)
{
lock(eLock)
{
// record only the first exception
if (_pendingException == null)
_pendingException = ex1;
}
}
There are multiple potential writers to _pendingException - multiple background threads - so I protect it with a lock.
In the main thread, must I take the lock before reading _pendingException
? Or can I simply do this:
if (_pendingException != null)
ThrowOrHandle();
EDIT:
ps: I would prefer to NOT take the lock on the reader thread because it is on the hot path, and I'd be taking and releasing the lock very, very often.You will not be able to get away this easy. You will lose exceptions if another thread throws it before the reader dealt with the existing one. What you need here is a synchronized queue:
try { ... stuff happens here... } catch (Exception ex1) { lock(queue) { queue.Enqueue(ex1); Monitor.PulseAll(queue); } }
And to process it:
while(!stopped) lock (queue) { while (queue.Count > 0) processException(queue.Dequeue()); Monitor.Wait(queue); }
Reads and writes to references are atomic (See C# Spec) and I'm nearly certain that lock does create a memory barrier so yes what you are doing is probably safe.
But really just use the lock around your read. It's guaranteed to work; if you every see it accessed not in a lock you know something is wrong, if the lock is causing you performance issues then you're checking the flag way too often, and it's just the "right thing to do."
Even though you may only care about the first exception, you may still want to use lock for at least two reasons:
- In multi-core CPUs, without making a variable volatile (or performing any memory barrier operation) , there might be a moment when threads running on different cores may see different values.
(I am not sure calling(update) Callinglock(queue)
in a worker thread will cause any memory barrier operation though).lock(queue)
in a worker thread will cause memory barrier operation as pointed out by Eric in the comment below.
2. Please keep it mind that References are not addresses (by Eric Lippert) (if you are assuming references are 32-bit addresses in 32-bit CLR that can be read atomically). The implementation of references can be changed to some opaque structures that may not be read atomically in future release of CLR (even though I think it is not likely to happen in foreseeable future :)) and your code will break.
精彩评论