繁体   English   中英

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

[英]How to prevent an exception in a background thread from terminating an application?

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

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

忽略未处理的异常可能很危险。 如果您有一个真正无法处理的异常(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。 当任何线程和任何类型的应用程序(有或没有用户界面)中出现未处理的异常时,将触发此事件。 然而,虽然它提供了一种很好的最后手段来记录未捕获的异常,但它没有提供防止应用程序关闭的方法——也没有提供抑制 .NET 未处理异常对话框的方法。

在生产应用程序中,所有线程入口方法都需要显式异常处理。 可以通过使用包装器或帮助器类来执行工作来减少工作,例如 BackgroundWorker(在第 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();
    };

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

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

这是一篇关于这个问题的很棒的博客文章: Handling "Unhandled Exceptions" in .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));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM