在不同线程中引发的 catch 异常

我的一个方法(Method1)产生一个新线程。 该线程执行一个方法(Method2) ,在执行期间抛出一个异常。 我需要获取调用方法(Method1)上的异常信息

有没有什么办法可以在 Method1中捕获这个在 Method2中抛出的异常?

111437 次浏览

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

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

  1. In a separate method: // You process exception in some task's thread

    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. In the same method: // You process exception in the caller's thread

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

Note that the exception which you get is AggregateException. All real exceptions are availible through ex.InnerExceptions property.

In .NET 3.5 you can use the following code:

  1. // You process exception in the child's thread

    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. Or // You process exception in the caller's thread

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

在不同线程之间共享数据的最简单方法是 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中读到这个方法,但是,我更喜欢在 O'Reilly book C# 3.0 in a nutshell中读到这个方法,由 Albahari 兄弟(2007) ,它也可以在 Google Books 上免费访问,就像这本书的新版本一样,因为它也涵盖了线程池,前景线程与背景线程等等,有漂亮而简单的示例代码。(免责声明: 我拥有一本破旧的这本书)

在制作 WinForms 应用程序时,使用共享数据特别方便,因为 WinForm 控件不是线程安全的。使用回调将数据从工作线程传递回 WinForm 控件,主 UI 线程需要使用 Invoke()的丑陋代码来使该控件线程安全。相反,使用共享数据和短为0.2秒的单线程 System.Windows.Forms.Timer,您可以很容易地在没有 Invoke的情况下将信息从工作线程发送到控件。

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

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

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

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


}