摘要:📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,用爱发电,去丈量人心,是否能达到人机合一?
📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
📢本文作者:由webmote 原创
📢作者格言:新的征程,用爱发电,去丈量人心,是否能达到人机合一?
大家好,今天咱们来聊一个.NET Framework里的“坑”——ThreadAbortException引发的无限循环问题。这个问题不仅有趣,还特别适合那些喜欢在代码里“玩火”的开发者们。咱们先来看看这个问题的背景,然后再聊聊如何避免它。
在.NET里,如果你想搞点并行/并发编程,通常会用到Task Parallel Library、Task、async/await这些现代编程神器。不过,有些时候你可能还是会手动管理线程,比如调用**Thread.Start**之类的。
但说实话,现在直接操作线程的场景已经不多了。大多数情况下,你应该用Task和ThreadPool来调度任务,用async/await来处理异步操作。不过,如果你真的有一个正在运行的线程,而你又想让它“停下来”,通常会使用CancellationToken这种“合作式取消”机制。但有时候,你可能控制不了线程里的代码(比如第三方库),这时候你就得祭出“核武器”——Thread.Abort。
注意:Thread.Abort只在.NET Framework里有效,.NET Core里不支持,调用它会直接抛出一个PlatformNotSupportedException。
2. Thread.Abort的工作原理当你调用Thread.Abort时,运行时会在目标线程的代码里抛出一个ThreadAbortException。这个异常比较特殊,你可以在代码里捕获它(不像StackOverflowException这种无法捕获的异常),但运行时会在catch块结束后自动重新抛出这个异常。
你可以通过调用**ResetAbort**来“取消”这个异常,但这篇文章里我们就不深入讨论这个了。
3. 一个简单的例子来看个简单的例子:
varmyThread =newThread(newThreadStart(DoWork));myThread.Start;
Thread.Sleep(300);
Console.WriteLine("Main - aborting thread");
myThread.Abort;// 触发ThreadAbortException
myThread.Join;// 等待线程退出
Console.WriteLine("Main ending");
staticvoidDoWork
{
try
{
for(vari =0 ; i 100; i++)
{
Console.WriteLine($"Thread - working {i}");
Thread.Sleep(100);
}
}
catch(ThreadAbortException e)
{
Console.WriteLine($"Thread - caught ThreadAbortException: {e.Message}");
// 即使我们捕获了异常,运行时还是会重新抛出它
}
// 这里永远不会执行
Console.WriteLine("Thread - outside the catch block");
}
运行这个程序,输出大概是这样的:
Thread - working 0Thread - working1
Thread - working2
Main - aborting thread
Thread - caught ThreadAbortException: Thread was being aborted.
Main ending
可以看到,即使我们捕获了ThreadAbortException,线程还是退出了,因为异常被重新抛出了。接下来我们来看一个不那么“正常”的情况。
这个问题是我们在2021年初在Datadog .NET Tracer里遇到的。当时在IIS的AppDomain回收过程中,应用程序无法正常关闭。问题的根源就是ThreadAbortException。
我们可以通过稍微修改上面的例子来复现这个问题。这次我们把for循环改成while循环,其他代码不变:
staticvoidDoWork{
vari =0 ;
while(true)
{
try
{
Console.WriteLine($"Thread - working {i}");
i++;
Thread.Sleep(100);
}
catch(ThreadAbortException e)
{
Console.WriteLine($"Thread - caught ThreadAbortException {e.Message}");
// 即使我们捕获了异常,运行时_应该_重新抛出它
}
}
// 这里永远不会执行
Console.WriteLine("Thread - outside the catch block.");
}
理论上,Abort被调用后,异常会被捕获,然后运行时应该重新抛出它,退出while循环并结束线程。但如果你在Release模式下运行这个程序,问题就来了——你会陷入catch块的无限循环中:
Thread - working 0Thread - working1
Thread - working2
Main - aborting thread.
Thread - caught ThreadAbortException Thread was being aborted.
...
这显然是个大问题,根本原因是JIT编译器的一个bug。这个bug的细节比较复杂(主要是为了解决另一个bug而引入的),但你可以通过设置来绕过它。
微软决定不修复这个bug,所以你得自己动手。如果你遇到了这种问题,最简单的解决办法是手动重新抛出ThreadAbortException。
如果你在写一个只支持**.NET Core的应用,那就不用担心这个问题,因为.NET Core不支持ThreadAbortException**。但如果你在写一个同时支持**.NET Core和.NET Framework**的库,那就需要你多留神了。
今天咱们聊了.NET Framework运行时的一个bug,它会导致ThreadAbortException陷入无限循环。这个bug只在你把try-catch块直接嵌套在while循环里时才会触发。通常,如果你捕获了ThreadAbortException,运行时会在catch块结束后自动重新抛出异常。但这个bug会导致catch块无限重复执行。
好了,今天的“坑”就聊到这里,希望大家在写代码时能避开这个雷区!如果你觉得这篇文章有用,别忘了点赞、分享,咱们下次再见!
👓都收藏了,还在乎一个评论吗?
来源:opendotnet