捕获在不同线程中引发的异常


109

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

是有什么方法我能赶上这个例外Method1是在抛出Method2

Answers:


181

.NET 4及更高版本中,您可以使用Task<T>class而不是创建新线程。然后,您可以使用.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. //您在线程中处理异常

    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。根据我的理解,任务是4.0件事?
Silverlight学生,

2
@SilverlightStudent好吧,我刚刚更新了我的答案以满足您的要求。
oxilumin 2011年

@oxilumin:谢谢,非常感谢。还有一个后续问题。如果您的Test()方法也接受一些参数,那么您将如何修改这些参数的SafeExecute方法?
Silverlight学生,

2
@SilverlightStudent在这种情况下,我将传递lambda而不是Test。像() => Test(myParameter1, myParameter2)
oxilumin

2
@SilverlightStudent:已更新。
oxilumin

9

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


感谢您的答复。因此,如果Method1是Class1的一部分,并且我在该类中具有Exception类型的变量。每当Method2引发异常时,它也会在Class1中设置该异常变量。听起来像一个公平的设计吗?有什么最佳实践方法可以处理这种情况?
Silverlight学生,

正确,您只存储异常并在以后访问它。将来运行的方法(特别是Method2完成时的回调)然后抛出该异常,就像它们本身是导致异常的情况并不少见,但这确实取决于您想要的内容。
ermau 2011年

0

在不同线程之间共享数据的最简单方法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;
   }
}

您可以在有关多线程的漂亮介绍中阅读此方法,但是,我更喜欢在O'Reilly book C# 3.0 in a nutshellAlbahari兄弟(2007)的上阅读有关此方法的信息,该书也可以在Google图书中免费获得,就像该书的新版本一样,因为它还通过漂亮而简单的示例代码介绍了线程池,前台线程与后台线程等。(免责声明:我拥有这本书的破旧副本)

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


0

我有一个特殊的问题,就是我想使用集成测试套件中包含控件的项目,因此必须创建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) { ... }

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.