我生成了一个前台线程和一个后台线程,在每个线程中抛出异常。
using System;
using System.Threading;
namespace OriginalCallStackIsLostOnRethrow
{
class Program
{
static void Main(string[] args)
{
try
{
A2();
// Uncomment this to see how the unhandled
// exception in the foreground thread causes
// the program to terminate
// An exception in this foreground thread
// *does* terminate the program
// var t = new Thread(() => {
// throw new DivideByZeroException();
// });
// t.Start();
}
catch (Exception ex)
{
// I am not expecting anything from the
// threads to come here, which is fine
Console.WriteLine(ex);
}
finally
{
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
static void A2() { B2(); }
static void B2() { C2(); }
static void C2() { D2(); }
static void D2()
{
Action action = () =>
{
Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2");
throw new DivideByZeroException();
Console.WriteLine("Do we get here? Obviously not!");
};
action.BeginInvoke(ar => Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"), null);
}
}
}
正如预期的那样,前台线程中的未处理异常终止了该进程。但是,后台线程中的未处理异常只是终止线程,并且不会使进程停止,实际上无法观察到并且无声地失败。
因此,该程序产生以下输出:
Press any key to exit...
D2 called on worker #6. Exception will occur while running D2
D2 completed on worker thread #6
这挑战了我对线程中异常处理的理解。我的理解是,无论线程的性质如何,从框架的v2.0开始,未处理的异常将使进程终止。
以下是the documentation关于此主题的引用:
线程的前景或后台状态不会影响 线程中未处理的异常的结果。在.NET Framework中 版本2.0,前景或后台中的未处理异常 线程导致应用程序终止。请参阅中的例外情况 管理线程。
此外,标题为Exceptions in Managed Threads的页面如下:
从.NET Framework 2.0版开始,这是一种通用语言 运行时允许线程中的大多数未处理的异常继续 自然。在大多数情况下,这意味着未处理的异常 导致应用程序终止。
这是.NET Framework 1.0及更高版本的重大更改 1.1,它为许多未处理的异常提供了一个支持 - 例如,线程池线程中的未处理异常。请参阅更改 本主题后面的早期版本。
另一项有趣的观察
有趣的是,如果我在完成回调中抛出异常而不是正在执行的实际操作,那么后台线程上的异常就会导致程序终止。有关代码,请参阅下文。
using System;
using System.Threading;
namespace OriginalCallStackIsLostOnRethrow
{
class Program
{
static void Main(string[] args)
{
try
{
// A2();
A3();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
static void A2() { B2(); }
static void B2() { C2(); }
static void C2() { D2(); }
static void D2()
{
Action action = () =>
{
try
{
Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2");
throw new DivideByZeroException();
// Console.WriteLine("Do we get here? Obviously not!");
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
};
action.BeginInvoke(ar => Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"), null);
}
static void A3() { B3(); }
static void B3() { C3(); }
static void C3() { D3(); }
static void D3()
{
Action action = () => { Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}."); };
action.BeginInvoke(ar =>
{
Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}. Oh, but wait! Exception!");
// This one on the completion callback does terminate the program
throw new DivideByZeroException();
}, null);
}
}
}
再次有趣的观察
此外,更有趣的是,如果您在要使用APM执行的操作中处理异常,则在catch
块中(在D2()
中的catch块中设置断点),出现的Exception
除了被调用的lambda之外没有堆栈跟踪。它甚至没有关于如何到达的信息。
然而,对于您在完成回调中的catch块中捕获的异常,情况并非如此,例如D3()
。
我在Visual Studio Community 2015 Edition中使用C#6.0编译器,我的程序目标是.NET框架的v4.5.2。
答案 0 :(得分:3)
正如PetSerAl在问题的评论部分指出的那样,要获取异常信息,必须从完成回调中调用EndInvoke
,如下所示。
using System;
using System.Runtime.Remoting.Messaging;
using System.Threading;
namespace OriginalCallStackIsLostOnRethrow
{
class Program
{
static void Main(string[] args)
{
try
{
A2();
// A3();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
static void A2() { B2(); }
static void B2() { C2(); }
static void C2() { D2(); }
static void D2()
{
Action action = () =>
{
Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2");
throw new DivideByZeroException();
};
action.BeginInvoke(ar =>
{
((Action)((ar as AsyncResult).AsyncDelegate)).EndInvoke(ar);
Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}");
}, null);
}
static void A3() { B3(); }
static void B3() { C3(); }
static void C3() { D3(); }
static void D3()
{
Action action = () => { Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}."); };
action.BeginInvoke(ar =>
{
try
{
Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}. Oh, but wait! Exception!");
throw new DivideByZeroException();
}
catch (Exception ex)
{
throw ex;
}
}, null);
}
}
}
这很奇怪,如果你要在异步执行的动作中放置一个try / catch
块,为什么堆栈跟踪不显示仍然是个谜。
我指的是缺少 StackTrace ,而不是没有调用堆栈。 : - )