简体   繁体   中英

How to catch exceptions from a ThreadPool.QueueUserWorkItem?

I have the following code that throws an exception:

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

You can add try/catch like this:

        ThreadPool.QueueUserWorkItem(state =>
                                         {
                                             try
                                             {
                                                 action();
                                             }
                                             catch (Exception ex)
                                             {
                                                 OnException(ex);
                                             }
                                         });

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.

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);

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;
    }

我通常做的是在action()方法中创建一个大的try ... catch块然后将异常存储为私有变量然后在主线程内处理它

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);
    }
}

I have a related question rather than an answer, so I thought I ask it here. I have quite opposite problem an exception in the ThreadPool.QueueUserWorkItem does not crash an application, but I want it to crash it. However, there is a difference. My ThreadPool.QueueUserWorkItem is wrapped up into an awaiter like this

   public struct YieldThreadPoolAwaiter : INotifyCompletion {
      public void OnCompleted(Action continuation) {
         if(continuation == null) {
            throw new ArgumentNullException(nameof(continuation));
         }
         ThreadPool.QueueUserWorkItem(RunAction, continuation);
      }

      public void GetResult() {
      }

      private static void RunAction(object state) {
         ((Action)state)();
      }

      public bool IsCompleted {
         get { return false; }
      }
   }

To me, it looks the same, there is still ThreadPool.QueueUserWorkItem call but it is called indirectly when I do

   await ThreadPoolEx.Yeild();

I see that the execution goes inside ThreadPool.QueueUserWorkItem, I know an exception is thrown there but the exception is somehow quietly swallowed, and the application does not crash.

The question is - WHY? What special .NET framework is doing that the application does not crash in this case and how to change it back?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM