views:

132

answers:

3

Related: How to catch exceptions from a ThreadPool.QueueUserWorkItem?

I am catching exceptions 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.

+3  A: 

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);
    }
mfeingold
I really just want the first exception - it's a fail fast approach. No recovery. So I don't care if I miss exceptions 2..n. I just want the first one.
Cheeso
+1  A: 

Even though you may only care about the first exception, you may still want to use lock for at least two reasons:

  1. 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 lock(queue) in a worker thread will cause any memory barrier operation though).

  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.

Chansik Im
This is NOT CORRECT. The specification clearly states that reads and writes of references must be atomic in all implementations of C#. See section 5.5 of the spec for details.
Eric Lippert
+2  A: 

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."

shf301
Problem is, the main thread is the one doing the read of that _pendingException field. 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.
Cheeso
Why do you not want to use the lock on the main thread? Have you tried it and found it to be too slow or is this premature optimization? If it is causing you a problem then your polling that flag way too often and should choose a different notification strategy, e.g. call a delegate on error rather than polling a flag.
shf301