如何在C#中处理/终止一个锁定的线程

3
我看到了很多关于如何终止锁定线程的问题,每一个答案都说不要杀死线程,甚至不要使用Thread.Abort,只需使用良好的编程实践来处理线程何时停止。
我的问题是,我被困在一个旧的第三方库中,我无法控制,有时这会锁定,几乎使用100%的CPU并且永远不会释放。Thread.Abort无法停止它。我找不到任何方法来停止它。
我找到的最好的“解决办法”是使用委托和.AsyncWaitHandle.WaitOne(10000, false)。超时后,代码将从此处继续执行,但仍然运行着的任何线程将继续运行并使用所有的CPU。
我该如何处理这种情况?如果这个第三方库锁定了,我需要一种方式来终止它。
谢谢您的时间, Ben

你如何调用这个库中的代码?如果是的话,你是否显式地为此创建了一个新线程?一小段代码将会很有用。 - Michał Komorowski
2
无法仅仅杀死线程。唯一可以安全终止的是整个进程。Thread.Abort 只能在线程执行托管代码时起作用,因此对于本地第三方库来说,它没有帮助。因此,将有问题的代码分离到一个新的简单进程中,从您的应用程序运行并在需要时终止它。 - Luaan
2个回答

5

如果你需要一个通用的解决方案,可以通过创建一个单独的进程来运行代码,这有助于防止代码被锁死。需要时可以结束该进程并重新启动。如果能更准确地了解问题,可能会有更好的解决方案出现。


1
我会创建一个单独的应用程序域来运行不受信任或偶尔出现问题的程序集,就像您的情况一样。应用程序域可以随时卸载,并在您的代码和外部代码之间创建边界。这是一个小例子。让我们从包含不正确代码的外部程序集开始:
public class Processor
{
    public void Run() 
    {
        while (true) { /* ... */ }                
    }
}

现在我们需要一个类来加载BadAssembly.dll,创建Processor的实例并在单独的线程中调用Run方法,以避免阻塞应用程序的主线程。
public class Loader : MarshalByRefObject
{
    public bool IsCompleted { get; private set; }
    public bool IsFaulted  { get; private set; }

    public void LoadAndRunBadAssembly()
    {
        var ass = Assembly.LoadFrom("BadAssembly.dll");
        var c = (Processor)ass.CreateInstance("BadAssembly.Processor");

        Task.Factory.StartNew(() =>
            {
                try
                {
                    c.Run();
                    IsCompleted = true;
                }
                catch (Exception)
                {
                    IsFaulted = true;
                }
            }, TaskCreationOptions.LongRunning);
    }
} 

IsCompletedIsFaulted属性用于告诉我们外部代码是否已经完成处理。 Loader继承自MarshalByRefObject,因为这样它的代码将在我们创建的单独域中执行。下面是一个测试Loader类的代码:

var appDomain = AppDomain.CreateDomain("Loader");
try
{
    //Here I assume that Loader class is in the same assembly as this code
    var loader = (Loader)appDomain.CreateInstanceAndUnwrap(
        Assembly.GetExecutingAssembly().FullName, 
        typeof (Loader).FullName);

    loader.LoadAndRunBadAssembly();

    Console.WriteLine("Waiting...");

    Thread.Sleep(20000); //This simulates waiting for external code

    if (loader.IsCompleted)
        Console.WriteLine("Processing has finished");
    else if (loader.IsFaulted)
        Console.WriteLine("There was an error");
    else
        Console.WriteLine("It lasts too long");
}
catch (Exception ex)
{
    Console.WriteLine(ex);
}
finally
{
    AppDomain.Unload(appDomain);
}

更新

我刚刚意识到,为了停止域中的线程,AppDomain.Unload方法使用Abort方法。这意味着当尝试卸载域时,您可能会收到CannotUnloadAppDomainException异常。这将发生是因为无法保证Abort会立即终止一个线程。


2
如果应用程序域中的线程无法被中止,则该域将无法卸载。 - usr
我运行了这段代码几次,都成功了。例如,尝试将Console.Writeline放置在Processor.Run方法的while循环内,并运行程序。20秒后,控制台中不会出现新消息。您还可以在Debug中运行我的代码,并观察Visual Studio中的模块窗口。20秒后,BadAssembly.dll将从列表中移除,这意味着应用程序域已卸载。类似的练习也可以在Threads窗口中进行。我漏掉了什么吗? - Michał Komorowski
是的,问题中提到的 Thread.Abort() 部分并不能停止它,这表明挂起发生在非托管代码中。 - Ben Voigt
@BenVoigt - 感谢你的澄清。 - Michał Komorowski
1
您可以通过编写 finally { Thread.Sleep(100000); } 来进行复现。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接