如何防止后台线程中的异常终止应用程序

本文关键字:异常终止 应用程序 线程 何防止 后台 | 更新日期: 2023-09-27 17:47:22

我可以连接到AppDomain.CurrentDomain.UnhandledException以记录来自后台线程的异常,但是如何防止它们终止运行时?

如何防止后台线程中的异常终止应用程序

首先,您确实应该尽量不要在后台线程中抛出异常 - 并且不处理异常。如果您控制委托的运行方式,请将其封装在 try catch 块中,并找出一种将异常信息传递回主线程的方法(如果您显式调用了 BeginInvoke,请使用 EndInvoke,或者通过在某处更新某些共享状态)。

忽略未经处理的异常可能很危险。如果你有一个真正的不可处理的异常(想到了OutOfMemoryException),无论如何你都无能为力,你的进程基本上注定要失败。

回到 .Net 1.1,后台线程中未经处理的异常将被抛到无处可去,主线程会很乐意继续前进。这可能会产生令人讨厌的影响。因此,在 .Net 2.0 中,此行为已更改。

现在,在不是主线程的线程中引发的未经处理的异常将终止进程。您可能会收到此通知(通过在 AppDomain 上订阅事件),但该过程仍将死亡。

由于这可能很不方便(当您不知道线程中将运行什么并且您不确定它是否受到正确保护,并且您的主线程必须具有弹性时),因此有一种解决方法。它旨在作为旧设置(这意味着,强烈建议您确保没有杂散线程),但您可以通过以下方式强制前一种行为:

只需将此设置添加到您的服务/应用程序/任何配置文件:

<configuration>
  <runtime>
    <!-- the following setting prevents the host from closing when an unhandled exception is thrown -->
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

不过,它似乎不适用于 ASP.NET。

有关详细信息(以及即将推出的 CLR 版本可能不支持此设置的巨大警告),请参阅 http://msdn.microsoft.com/en-us/library/ms228965.aspx

来自 Joe Albahari 的优秀线程文章:

.NET 框架提供了一个 全局异常的较低级别事件 处理: AppDomain.UnhandledException. This 事件在未处理时触发 任何线程和任何线程中的异常 应用程序类型(带或不带 用户界面)。然而,虽然它 提供良好的最后手段机制 对于记录未捕获的异常,它 不提供防止 从关闭申请 – 和 无法抑制 .NET 未处理的异常对话框。

在生产应用程序中,显式 所有都需要异常处理 线程输入方法。一个人可以削减 使用包装器或帮助程序工作 类来执行作业,例如 背景工作者(在部分中讨论 3).

保持答案简短,是的,你可以防止运行时终止。

以下是解决方法的演示:

class Program
{
    void Run()
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        Console.WriteLine("Press enter to exit.");
        do
        {
            (new Thread(delegate()
            {
                throw new ArgumentException("ha-ha");
            })).Start();
        } while (Console.ReadLine().Trim().ToLowerInvariant() == "x");

        Console.WriteLine("last good-bye");
    }
    int r = 0;
    void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Interlocked.Increment(ref r);
        Console.WriteLine("handled. {0}", r);
        Console.WriteLine("Terminating " + e.IsTerminating.ToString());
        Thread.CurrentThread.IsBackground = true;
        Thread.CurrentThread.Name = "Dead thread";            
        while (true)
            Thread.Sleep(TimeSpan.FromHours(1));
        //Process.GetCurrentProcess().Kill();
    }
    static void Main(string[] args)
    {
        Console.WriteLine("...");
        (new Program()).Run();
    }
}

从本质上讲,您只是没有让运行时显示"...程序已停止工作"对话框。

如果您需要记录异常并以静默方式退出,您可以调用Process.GetCurrentProcess().Kill();

    AppDomain.CurrentDomain.UnhandledException += (sender, e2) =>
    {
        Thread.CurrentThread.Join();
    };

但请注意,此代码将冻结线程和线程的托管对象本身的所有堆栈内存。但是,如果您的应用程序处于确定状态(可能是您抛出了 LimitedDemoFeatureException 或 OperationStopWithUserMessageException),并且您没有开发 24/7 应用程序,则此技巧将起作用。

最后,我认为 MS 应该允许开发人员从堆栈顶部覆盖未经处理的异常的逻辑。

下面是一篇关于这个问题的很棒的博客文章: 在 .NET 2.0 中处理"未经处理的异常"

IMO 手动处理后台线程中的异常并在必要时通过回调重新抛出它们是正确的。

delegate void ExceptionCallback(Exception ex);
void MyExceptionCallback(Exception ex)
{
   throw ex; // Handle/re-throw if necessary
}
void BackgroundThreadProc(Object obj)
{
   try 
   { 
     throw new Exception(); 
   }
   catch (Exception ex)
   { 
     this.BeginInvoke(new ExceptionCallback(MyExceptionCallback), ex); 
   }
}
private void Test()
{
   ThreadPool.QueueUserWorkItem(new WaitCallback(BackgroundThreadProc));
}