【问题标题】:catch exception that is thrown in different thread捕获在不同线程中抛出的异常
【发布时间】:2011-08-24 10:28:01
【问题描述】:

我的一个方法 (Method1) 产生了一个新线程。 该线程执行一个方法(Method2),并在执行期间引发异常。 我需要获取有关调用方法的异常信息 (Method1)

有什么方法可以在Method1 中捕获这个异常,该异常是在Method2 中抛出的?

【问题讨论】:

    标签: c# multithreading exception-handling


    【解决方案1】:

    .NET 4 及更高版本中,您可以使用Task<T> 类而不是创建新线程。然后,您可以使用任务对象上的 .Exceptions 属性获取异常。 有两种方法:

    1. 在一个单独的方法中: // 你在一些任务的线程中处理异常

      class Program
      {
          static void Main(string[] args)
          {
              Task<int> task = new Task<int>(Test);
              task.ContinueWith(ExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
              task.Start();
              Console.ReadLine();
          }
      
          static int Test()
          {
              throw new Exception();
          }
      
          static void ExceptionHandler(Task<int> task)
          {
              var exception = task.Exception;
              Console.WriteLine(exception);
          }
      }
      
    2. 在同一个方法中://你在调用者的线程中处理异常

      class Program
      {
          static void Main(string[] args)
          {
              Task<int> task = new Task<int>(Test);
              task.Start();
      
              try
              {
                  task.Wait();
              }
              catch (AggregateException ex)
              {
                  Console.WriteLine(ex);    
              }
      
              Console.ReadLine();
          }
      
          static int Test()
          {
              throw new Exception();
          }
      }
      

    请注意,您得到的异常是AggregateException。所有真正的异常都可以通过ex.InnerExceptions 属性获得。

    .NET 3.5 中可以使用以下代码:

    1. // 你在child的线程中处理异常

      class Program
      {
          static void Main(string[] args)
          {
              Exception exception = null;
              Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), Handler));
              thread.Start();            
      
              Console.ReadLine();
          }
      
          private static void Handler(Exception exception)
          {        
              Console.WriteLine(exception);
          }
      
          private static void SafeExecute(Action test, Action<Exception> handler)
          {
              try
              {
                  test.Invoke();
              }
              catch (Exception ex)
              {
                  Handler(ex);
              }
          }
      
          static void Test(int a, int b)
          {
              throw new Exception();
          }
      }
      
    2. 或者 // 你在调用者的线程中处理异常

      class Program
      {
          static void Main(string[] args)
          {
              Exception exception = null;
              Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), out exception));
      
              thread.Start();            
      
              thread.Join();
      
              Console.WriteLine(exception);    
      
              Console.ReadLine();
          }
      
          private static void SafeExecute(Action test, out Exception exception)
          {
              exception = null;
      
              try
              {
                  test.Invoke();
              }
              catch (Exception ex)
              {
                  exception = ex;
              }
          }
      
          static void Test(int a, int b)
          {
              throw new Exception();
          }
      }
      

    【讨论】:

    • 抱歉,我忘了说我使用的是 .NET 3.5。据我了解,Task 是 4.0 的东西?
    • @SilverlightStudent 好的,我刚刚更新了答案以满足您的要求。
    • @oxilumin:谢谢,非常感谢。还有一个后续问题。如果您的 Test() 方法也需要一些参数,那么您将如何修改这些参数的 SafeExecute 方法?
    • @SilverlightStudent 在这种情况下,我将传递一个 lambda 而不是 Test。喜欢() =&gt; Test(myParameter1, myParameter2)
    • @SilverlightStudent:已更新。
    【解决方案2】:

    您无法在 Method1 中捕获异常。但是,您可以在 Method2 中捕获异常并将其记录到一个变量中,然后原始执行线程可以读取并使用它。

    【讨论】:

    • 感谢您的回复。因此,如果 Method1 是 Class1 的一部分,并且我在该类中有一个 Exception 类型的变量。每当 Method2 抛出异常时,它也会在 Class1 中设置该异常变量。这听起来像一个公平的设计吗?是否有处理这种情况的最佳实践方法?
    • 正确,您只需存储异常并稍后访问它。未来运行的方法(尤其是 Method2 完成时的回调)然后重新抛出异常的情况并不少见,就好像它们自己造成了它一样,但这真的取决于你想要什么。
    【解决方案3】:

    不同线程之间共享数据最简单的方法是shared data,如下(有些是伪代码):

    class MyThread
    {
       public string SharedData;
    
       public void Worker()
       {
          ...lengthy action, infinite loop, etc...
          SharedData = "whatever";
          ...lengthy action...
          return;
       }
    }
    
    class Program
    {
       static void Main()
       {
          MyThread m = new MyThread();
          Thread WorkerThread = new Thread(m.Worker);
          WorkerThread.Start();
    
          loop//or e.g. a Timer thread
          {
             f(m.SharedData);
          }
          return;
       }
    }
    

    您可以在this nice introduction about multithreading 中阅读有关此方法的信息,但是,我更喜欢在 Albahari 兄弟 (2007) 的 O'Reilly book C# 3.0 in a nutshell 中阅读有关此方法的信息,该方法也可以在 Google 图书上免费访问,就像较新的版本一样本书,因为它还涵盖了线程池,前台与后台线程等,以及漂亮而简单的示例代码。 (免责声明:我拥有这本书的旧版)

    如果您正在制作 WinForms 应用程序,使用共享数据特别方便,因为 WinForm 控件不是线程安全的。使用回调将数据从工作线程传递回 WinForm 控件,主 UI 线程需要带有 Invoke() 的丑陋代码,以使该控件线程安全。改用共享数据和单线程System.Windows.Forms.Timer,短的Interval 0.2 秒,您可以轻松地将信息从工作线程发送到控件,而无需Invoke

    【讨论】:

      【解决方案4】:

      我有一个特殊的问题,我想使用来自集成测试套件的包含控件的项目,因此必须创建一个 STA 线程。我最终得到的代码如下,放在这里以防其他人有同样的问题。

          public Boolean? Dance(String name) {
      
              // Already on an STA thread, so just go for it
              if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) return DanceSTA(name);
      
              // Local variable to hold the caught exception until the caller can rethrow
              Exception lException = null;
      
              Boolean? lResult = null;
      
              // A gate to hold the calling thread until the called thread is done
              var lGate = new ManualResetEvent(false);
      
              var lThreadStart = new ThreadStart(() => {
                  try {
                      lResult = DanceSTA(name);
                  } catch (Exception ex) {
                      lException = ex;
                  }
                  lGate.Set();
              });
      
              var lThread = new Thread(lThreadStart);
              lThread.SetApartmentState(ApartmentState.STA);
              lThread.Start();
      
              lGate.WaitOne();
      
              if (lException != null) throw lException;
      
              return lResult;
          }
      
          public Boolean? DanceSTA(String name) { ... }
      

      这是按原样直接粘贴的代码。对于其他用途,我建议提供一个操作或函数作为参数并在线程上调用它,而不是对调用的方法进行硬编码。

      【讨论】:

        【解决方案5】:

        这是我用来将异常抛出回要捕获的主线程的代码。

        class Program
        {
            static void Main(string[] args)
            {
                CancellationTokenSource cancelToken = new CancellationTokenSource();
                Exception taskException = null;
        
                var timerTask = Task.Factory.StartNew(() =>
                {
                    for (;;)
                    {
                        if (cancelToken.IsCancellationRequested)
                            break;
        
                        ContinuousTask();
        
                        Thread.Sleep(400);
        
                    }
                }, cancelToken.Token).ContinueWith((t, o) => {
                    taskException = t.Exception;
                    ((Thread)o).Interrupt();
                }, Thread.CurrentThread, TaskContinuationOptions.OnlyOnFaulted);
        
                try
                {
                    
                    //do a bunch of tasks here
        
                    //want to skip the do while and go to the catch if exception is thrown
                    do
                    {
                        System.Threading.Thread.Sleep(200);
                    } while (true);
        
                }
                catch
                {
                    if (taskException != null)
                        Console.WriteLine(taskException.Message);
                }
                
            }
        
            private static int _loopCounter = 0;
            public static void ContinuousTask()
            {
                int counter = 0;
        
                do
                {
        
                    if (_loopCounter >= 3)
                        throw new Exception("error");
        
                    if (counter >= 5)
                        break;
        
                    counter += 1;
                    System.Threading.Thread.Sleep(100);
        
                } while (true);
        
                _loopCounter += 1;
            }
        
        }
        

        【讨论】:

          猜你喜欢
          • 2014-05-20
          • 1970-01-01
          • 2011-12-05
          • 2018-03-10
          • 1970-01-01
          • 2016-02-17
          • 2013-04-14
          • 1970-01-01
          • 2012-06-08
          相关资源
          最近更新 更多