【问题标题】:How to catch exceptions from a ThreadPool.QueueUserWorkItem?如何从 ThreadPool.QueueUserWorkItem 中捕获异常?
【发布时间】:2009-04-15 21:37:31
【问题描述】:

我有以下引发异常的代码:

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

当操作引发异常时,我的程序崩溃。处理这种情况的最佳做法是什么?


相关:Exceptions on .Net ThreadPool Threads

【问题讨论】:

  • 有了这个问题,你刚刚救了我的命;)我的 IIS 崩溃了,我不知道问题出在哪里......这是一个这样的问题:D

标签: c# .net multithreading threadpool


【解决方案1】:

你可以像这样添加 try/catch:

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

【讨论】:

  • +1 用于酷炫的 lambda。我发现如果线程池中的工作线程在我的 Web 应用程序中抛出未处理的异常,它会导致整个 Web 服务器崩溃。这救了我!谢谢!
【解决方案2】:

如果您有权访问action 的源代码,请在该方法中插入一个 try/catch 块;否则,创建一个新的 tryAction 方法,它将对 action 的调用包装在 try/catch 块中。

【讨论】:

    【解决方案3】:

    如果您使用的是 .Net 4.0,则可能值得研究 Task 类,因为它可以为您解决这个问题。

    相当于您的原始代码,但使用任务,看起来像

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

    要处理异常,您可以向 StartNew 返回的任务添加延续。它可能看起来像这样:

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

    【讨论】:

    【解决方案4】:

    在另一个线程上,(在您“排队”的方法中,添加一个 try catch 子句...。然后在 catch 中,将捕获的异常放入共享的异常变量中(对主线程可见)。

    然后在您的主线程中,当所有排队的项目都完成时(为此使用等待句柄数组)检查是否某个线程用异常填充了该共享异常...如果确实如此,请重新抛出它或酌情处理它。 ..

    这是我最近使用的一个项目中的一些示例代码...
    HasException 是共享布尔值...

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

    【讨论】:

    • 什么是“共享异常变量(对主线程可见)”?如果抛出多个异常怎么办?
    • 在上面的示例代码中,X 是共享的异常变量...它只是在类级别声明的变量,因此它在类中的所有方法之间“共享”...并且只要第一个抛出异常,你不想停止“吗?
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      简单代码:

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

      【讨论】:

        猜你喜欢
        • 2018-07-14
        • 2019-05-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-26
        • 2010-12-27
        相关资源
        最近更新 更多