我有一个WinForms应用程序,它创建了多个表单,每个表单都在自己的GUI线程中(不是主GUI线程).我想处理所有这些表单的Unhandled Exception事件(Application.ThreadException)来处理任何错误.我还想处理工作线程的异常 – 这一点似乎工作正常,但我仍然遇到GUI线程异常的问题:
Program.cs中:
[STAThread]
static void Main()
{
AttachExceptionHandlers();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
public static void AttachExceptionHandlers()
{
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Dispatcher.CurrentDispatcher.UnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(CurrentDispatcher_UnhandledException);
}
Form1.cs中:
//GUI Thread Crash
private void button1_Click(object sender, EventArgs e)
{
object a = null;
a.ToString();
}
//Worker Thread Crash
private void button2_Click(object sender, EventArgs e)
{
Thread myThread = new Thread(() =>
{
object a = null;
a.ToString();
});
myThread.Start();
myThread.Join();
}
//New Thread, New Gui Crash
private void button3_Click(object sender, EventArgs e)
{
Thread myThread = new Thread(() =>
{
using (CrashingForm form = new CrashingForm()) //Crashing Form Crashes in it's FormLoad event.
{
Application.Run(form);
}
});
myThread.Start();
myThread.Join();
}
此代码将在前2个实例(GUI Thread Crash和Worker Thread Crash)中调用我的异常处理程序,但不处理创建新GUI线程的第三个实例.我发现如果我调用Program.AttachExceptionHandlers();在Application.Run(form)行之前,一切正常,但这是不可取的,因为我必须实现一些逻辑以确保在我们调用每个线程上创建一个表单之前调用Program.AttachExceptionHandlers()(如果在线程上创建表单后调用,则对Application.SetUnhandledExceptionMode的调用失败.
这个例子是更大一部分代码的一部分,理想情况下,我的代码用户可以在应用程序启动时调用一个简单的API(比如在Program.cs中)来附加异常处理程序.然后,异常处理程序会记录有关在应用程序死亡之前抛出的异常的详细信息.因此告诉用户他们每次创建新的GUI线程时都必须跟踪(工作线程似乎不受此问题的影响)并重新附加Application.ThreadException Handler并不是一个干净的解决方案.
有没有其他方法可以实现这一点,而无需在每次创建新的GUI线程时重新注册Application.ThreadException事件?
最佳答案
Is there another way to achieve this, without having to re registerer
for the Application.ThreadException event each time a new GUI thread
is created?
我不知道,我的队友和我花了很多时间研究它.在涉及如何创建/管理/销毁多个消息泵时,.NET WinForms似乎并不是很自以为是.
除了Retlang’s WinFormsFiber之外,我们使用类似于下面的框架方法.
using System;
using System.Threading;
using System.Windows.Forms;
internal static class Program
{
[STAThread]
private static void Main()
{
CreateFormAndStartMessagePump(() => CreateForm("first"), OnException, OnException, false, "pumpThread1");
CreateFormAndStartMessagePump(() => CreateForm("second"), OnException, OnException, false, "pumpThread2");
// note app shutdown not handled here
}
private static T CreateFormAndStartMessagePump<T>(
Func<T> createForm,
ThreadExceptionEventHandler onThreadException,
UnhandledExceptionEventHandler onDomainException,
bool isBackground,
string name) where T : Form
{
var latch = new ManualResetEvent(false);
T form = null;
var thread = new Thread(ts =>
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += onThreadException;
AppDomain.CurrentDomain.UnhandledException += onDomainException;
form = createForm();
latch.Set();
Application.Run();
})
{
IsBackground = isBackground,
Name = name
};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
latch.WaitOne();
return form;
}
private static Form CreateForm(string name)
{
var form = new Form();
form.Text = name;
form.Show();
return form;
}
private static void OnException(object sender, UnhandledExceptionEventArgs e)
{
// ...
}
private static void OnException(object sender, ThreadExceptionEventArgs e)
{
// ...
}
}