2

I often have to execute code on a separate thread that is long running, blocking, instable and\or has a potential to hang forever. Since the existence of TPL the internet is full of examples that nicely cancel a task with the cancellation token but I never found an example that kills a task that hangs. Code that hangs forever is likely to be expected as soon as you communicate with hardware or call some third party code. A task that hangs cannot check the cancellation token and is doomed to stay alive forever. In critical applications I equip those tasks with alive signals that are sent on regular time intervals. As soon as a hanging task is detected, it is killed and a new instance is started.

The code below shows an example task that calls a long running placeholder method SomeThirdPartyLongOperation() which has the potential to hang forever. The StopTask() first checks if the task is still running an tries to cancel it with the cancellation token. If that doesn’t work, the task hangs and the underlying thread is interrupted\aborted old school style.

    private Task _task;
    private Thread _thread;
    private CancellationTokenSource _cancellationTokenSource;

    public void StartTask()
    {
        _cancellationTokenSource = new CancellationTokenSource();
        _task = Task.Factory.StartNew(() => DoWork(_cancellationTokenSource.Token), _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    }

    public void StopTask()
    {
        if (_task.Status == TaskStatus.RanToCompletion)
            return;
        _cancellationTokenSource.Cancel();
        try
        {
            _task.Wait(2000); // Wait for task to end and prevent hanging by timeout.
        }
        catch (AggregateException aggEx)
        {
            List<Exception> exceptions = aggEx.InnerExceptions.Where(e => !(e is TaskCanceledException)).ToList(); // Ignore TaskCanceledException
            foreach (Exception ex in exceptions)
            {
                // Process exception thrown by task
            }
        }
        if (!_task.IsCompleted) // Task hangs and didn't respond to cancellation token => old school thread abort
        {
            _thread.Interrupt();
            if (!_thread.Join(2000))
            { 
                _thread.Abort();
            }
        }
        _cancellationTokenSource.Dispose();
        if (_task.IsCompleted)
        {
            _task.Dispose();
        }
    }

    private void DoWork(CancellationToken cancellationToken)
    {
        if (string.IsNullOrEmpty(Thread.CurrentThread.Name)) // Set thread name for debugging
            Thread.CurrentThread.Name = "DemoThread";
        _thread = Thread.CurrentThread; // Save for interrupting/aborting if thread hangs
        for (int i = 0; i < 10; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            SomeThirdPartyLongOperation(i);
        }
    }

Although I’ve been using this construct for some years now, I want to know if there are some potential mistakes in it. I’ve never seen an example of a task that saves the underlying thread or gives it a name to simplify debugging, so I’m a bit unsure if this is the right way to go. Comment on any detail is welcome!

4

2 回答 2

2

一旦您与硬件通信或调用某些第三方代码,就可能会出现永远挂起的代码。

沟通:绝对不是。总有一种方法可以让通信 API 超时,因此即使使用行为不端的硬件,也无需强制终止 I/O 操作。

第三方代码:仅当您偏执(或有高要求,例如 24x7 自动化)时。

这是底线:

  • 没有办法强制终止任务。
  • 您可以强制终止线程,但这很容易导致应用程序状态出现严重问题,可能会在代码的其他部分引入死锁以及资源泄漏。
  • 您可以强制杀死一个应用程序域,这可以解决大部分应用程序状态/死锁问题与杀死线程。但是,它并没有解决所有问题,并且仍然存在资源泄漏的问题。
  • 您可以强制终止进程。这是唯一真正干净可靠的解决方案。

因此,如果您选择信任第三方代码,我建议您像调用任何其他 API 一样调用它。如果你需要 100% 的可靠性而不考虑第三方库,你需要将第三方 dll 包装到一个单独的进程中,并使用跨进程通信来调用它。

您当前的代码强制杀死一个线程池线程,这当然不推荐;这些线程属于线程池,不属于你,即使你指定LongRunning. 如果你走 kill-thread 路线(这并不完全可靠),那么我建议使用显式线程。

于 2016-08-17T16:20:32.390 回答
0

问题是为什么这项任务甚至完全悬而未决?我认为这个问题没有通用的解决方案,但你应该专注于始终负责的任务,而不是强迫中断它。

在这段代码中,看起来你正在寻找一个简单的线程而不是一个任务——你不应该将任务链接到线程——在一些异步操作之后任务很可能会切换到另一个线程,你最终会在杀死一个不再连接到您的任务的无辜线程。如果你真的需要杀死整个线程,那么就为这项工作制作一个专用线程。

您也不应该对用于任务默认池的任何线程命名或做任何事情。考虑这段代码:

static void Main(string[] args)
{
    Task.Run(sth);
    Console.Read();
}

static async Task sth()
{
    Thread.CurrentThread.Name = "My name";
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(1);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(Thread.CurrentThread.Name ?? "No name");
}

输出是:

3
4
No name
于 2016-08-17T15:57:28.820 回答