我经常需要在一个长时间运行,阻塞,不稳定和\或可能永久挂起的单独线程上执行代码。由于TPL的存在,互联网上充满了使用取消令牌很好地取消任务的示例,但我从未找到过杀死挂起任务的示例。一旦与硬件通信或调用某些第三方代码,就可能会出现永远挂起的代码。挂起的任务无法检查取消令牌,注定永远保持活着。在关键应用中,我为这些任务配备了定期发送的有效信号。一旦检测到挂起任务,它就会被终止并启动一个新实例。
下面的代码显示了一个示例任务,它调用了一个长期运行的占位符方法SomeThirdPartyLongOperation(),它有可能永远挂起。 StopTask()首先检查任务是否仍在运行尝试使用取消令牌取消它。如果这不起作用,则任务挂起并且底层线程被中断\中止旧学校风格。
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);
}
}
虽然我已经使用这种结构多年了,但我想知道它是否存在一些潜在的错误。我从来没有见过一个保存底层线程的任务的例子,或者给它起一个简化调试的名称,所以我有点不确定这是否是正确的方法。欢迎评论任何细节!
答案 0 :(得分:2)
一旦与硬件通信或调用某些第三方代码,就可能会出现永远挂起的代码。
沟通:绝对没有。总有一种方法可以通过通信API进行超时,因此即使硬件行为不当,也无需强行终止I / O操作。
第三方代码:只有你是偏执狂(或者具有24x7自动化等高要求)。
这是底线:
因此,如果您选择信任第三方代码,我建议您像任何其他API一样调用它。如果您需要100%的可靠性而不管第三方库,您需要将第三方dll包装到一个单独的进程中,并使用跨进程通信来调用它。
你当前的代码强制杀死线程池线程,这当然不推荐;这些线程属于线程池,而不属于您,即使您指定LongRunning
,这仍然是正确的。如果你去kill-thread路由(这不完全可靠),那么我建议使用一个显式线程。
答案 1 :(得分:0)
问题是为什么这个任务甚至都悬而未决?我认为这个问题没有通用的解决办法,但你应该把重点放在总是负责的任务上,而不是强迫中断它。
在这段代码中,您似乎正在寻找一个简单的线程而不是一个任务 - 您不应该将任务链接到线程 - 很可能该任务将切换到另一个在一些异步操作之后进行线程,你将最终杀死一个未连接到你的任务的一个innoccent线程。如果你真的需要杀死整个线程,那就专门为这个工作做一个专门的。
您不应该使用任何用于任务的线程来命名或执行任何操作。默认池。请考虑以下代码:
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