45

I have the following code that throws an exception:

ThreadPool.QueueUserWorkItem(state => action());

When the action throws an exception, my program crashes. What is the best practice for handling this situation?


Related: Exceptions on .Net ThreadPool Threads

Community
  • 1
  • 1
Michael Hedgpeth
  • 7,732
  • 10
  • 47
  • 66
  • With this question you just saved my life ;) My IIS was crashing and I didn't know what was the problem... it was a problem like this :D – rpf Feb 10 '12 at 20:23

6 Answers6

72

You can add try/catch like this:

        ThreadPool.QueueUserWorkItem(state =>
                                         {
                                             try
                                             {
                                                 action();
                                             }
                                             catch (Exception ex)
                                             {
                                                 OnException(ex);
                                             }
                                         });
Prankster
  • 4,031
  • 5
  • 33
  • 44
  • 10
    +1 for a cool use of a lambda. I found that if a worker thread in a thread pool threw an unhandled exception in my web app, it would crash the entire web server. This saved me! Thanks! – CodingWithSpike Mar 23 '10 at 17:50
25

If you have access to action's source code, insert a try/catch block in that method; otherwise, create a new tryAction method which wraps the call to action in a try/catch block.

Tormod Fjeldskår
  • 5,952
  • 1
  • 29
  • 47
19

If you're using .Net 4.0, it might be worth investigating the Task class because it can take care of this for you.

The equivalent of your original code, but using Tasks, looks like

Task.Factory.StartNew(state => action(), state);

To deal with exceptions you can add a continuation to the Task returned by StartNew. It might look like this:

var task = Task.Factory.StartNew(state => action(), state);
task.ContinueWith(t => 
     {
        var exception = t.Exception.InnerException;
        // handle the exception here
        // (note that we access InnerException, because tasks always wrap
        // exceptions in an AggregateException)
     }, 
     TaskContinuationOptions.OnlyOnFaulted);
Samuel Jack
  • 32,712
  • 16
  • 118
  • 155
  • You should consider to use Task.Run as it's recommended alternative [What is the difference between Task.Run() and Task.Factory.StartNew()](//stackoverflow.com/a/38423486) . Example of Task.Run with ErrorHandling see [Try Catch outside of: await Task.Run(()](//stackoverflow.com/a/17706799) – Michael Freidgeim May 10 '21 at 06:30
3

On the other thread, (in the method you are "queueing" up, add a try catch clause... .Then in the catch, place the caught exception into a shared Exception variable (visible to the main thread).

Then in your main thread, when all queued items have finished (use a wait handle array for this) Check if some thread populated that shared exception with an exception... If it did, rethrow it or handle it as appropriate...

here's some sample code from a recent project I used this for...
HasException is shared boolean...

    private void CompleteAndQueuePayLoads(
           IEnumerable<UsagePayload> payLoads, string processId)
    {
        List<WaitHandle> waitHndls = new List<WaitHandle>();
        int defaultMaxwrkrThreads, defaultmaxIOThreads;
        ThreadPool.GetMaxThreads(out defaultMaxwrkrThreads, 
                                 out defaultmaxIOThreads);
        ThreadPool.SetMaxThreads(
            MDMImportConfig.MAXCONCURRENTIEEUSAGEREQUESTS, 
            defaultmaxIOThreads);
        int qryNo = 0;
        foreach (UsagePayload uPL in payLoads)
        {
            ManualResetEvent txEvnt = new ManualResetEvent(false);
            UsagePayload uPL1 = uPL;
            int qryNo1 = ++qryNo;
            ThreadPool.QueueUserWorkItem(
                delegate
                    {
                        try
                        {
                            Thread.CurrentThread.Name = processId + 
                                                      "." + qryNo1;
                            if (!HasException && !uPL1.IsComplete)
                                 IEEDAL.GetPayloadReadings(uPL1, 
                                                  processId, qryNo1);
                            if (!HasException) 
                                UsageCache.PersistPayload(uPL1);
                            if (!HasException) 
                                SavePayLoadToProcessQueueFolder(
                                             uPL1, processId, qryNo1);
                        }
                        catch (MeterUsageImportException iX)
                        {
                            log.Write(log.Level.Error,
                               "Delegate failed "   iX.Message, iX);
                            lock (locker)
                            {
                                HasException = true;
                                X = iX;
                                foreach (ManualResetEvent 
                                          txEvt in waitHndls)
                                    txEvt.Set();
                            }
                        }
                        finally { lock(locker) txEvnt.Set(); }
                    });
            waitHndls.Add(txEvnt);
        }
        util.WaitAll(waitHndls.ToArray());
        ThreadPool.SetMaxThreads(defaultMaxwrkrThreads, 
                                 defaultmaxIOThreads);

        lock (locker) if (X != null) throw X;
    }
Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • what is a "shared Exception variable (visible to the main thread)"? What if multiple exceptions are thrown? – MedicineMan Apr 15 '09 at 21:42
  • In sample code above, X is the shared Exception variable... Its just a variable declared at class level so it is "shared" among all methods in class... And As soon as the first exception is thrown, don't you kinda want to stop "? – Charles Bretana Apr 15 '09 at 21:47
1

What I usually do is to create a big try ... catch block inside the action() method then store the exception as a private variable then handle it inside the main thread

oscarkuo
  • 10,431
  • 6
  • 49
  • 62
0

Simple Code:

public class Test
{
    private AutoResetEvent _eventWaitThread = new AutoResetEvent(false);

    private void Job()
    {
        Action act = () =>
        {
            try
            {
                // do work...
            }
            finally
            {
                _eventWaitThread.Set();
            }
        };
        ThreadPool.QueueUserWorkItem(x => act());
        _eventWaitThread.WaitOne(10 * 1000 * 60);
    }
}
SHO
  • 25
  • 8