按时间取消任务

我有一个multithreading应用程序,我需要在一定时间后取消每个任务,即使在取消时,他们使用非托管资源。 现在我使用以下代码(例如,控制台应用程序)。 在实际应用中,延迟可能发生在非托管资源中。

static void Main() { for (int i = 0; i < 10; i++) { Task.Factory.StartNew(Do, TaskCreationOptions.LongRunning); } Console.ReadLine(); } private static void Do() { new Timer(Thread.CurrentThread.Abort, null, 1000, -1); try { Console.WriteLine("Start " + Task.CurrentId); Thread.Sleep(2000); Console.WriteLine("End " + Task.CurrentId); } catch (Exception) { Console.WriteLine("Thread Aborted " + Task.CurrentId); } } 

得到结果:

在此处输入图像描述

但从安全角度来看,我不确定它是否适合真正的应用。 我也在不同的变种中使用了CancellationToken,但它没有给我正确的结果,因为我使用CancellAfter()或.Delay()的时间跨度和取消任务后,我获得了以下结果:

 static void Main() { for (int i = 0; i  { Task.Delay(2000).ContinueWith(_ => { clt.Cancel(); }, clt.Token); Do(clt.Token); }, clt.Token); task.Start(); } Console.ReadLine(); } private static void Do(CancellationToken cltToken) { Console.WriteLine("Start " + Task.CurrentId); Thread.Sleep(2500); if (!cltToken.IsCancellationRequested) { Console.WriteLine("End " + Task.CurrentId); } else { Console.WriteLine("Cancelled "+ Task.CurrentId); } } 

在此处输入图像描述

在这种情况下,必须取消所有任务,因为Thread.Sleep()>的时间分配用于执行每个任务。 但是我们可以看到有些时候要执行。

我也使用以下结构并给出相同的结果:

  static void Main() { for (int i = 0; i < 10; i++) { var clt = new CancellationTokenSource(); clt.CancelAfter(2000); Task.Factory.StartNew(Do, clt.Token); } Console.ReadLine(); } private static void Do(object obj) { var cltToken = (CancellationToken) obj; Console.WriteLine("Start " + Task.CurrentId); Thread.Sleep(2500); if (!cltToken.IsCancellationRequested) { Console.WriteLine("End " + Task.CurrentId); } else { Console.WriteLine("Cancelled "+ Task.CurrentId); } } 

在此处输入图像描述

我也使用Parallel并初始化取消令牌内部方法Do(),并使用Timer在时间跨度后取消令牌,但都给出相同的结果。

那么,为什么会发生这种情况,以及在一定时间后取消任务的正确方法是什么?

通过使用相同的计时,您可以获得与原始“中止”版本相同的结果。 例如,这段代码:

 static void Main() { var clt = new CancellationTokenSource(); clt.CancelAfter(1000); for (int i = 0; i < 10; i++) { Task.Run(() => Do(clt.Token)); } Console.ReadLine(); } private static void Do(CancellationToken cltToken) { Console.WriteLine("Start " + Task.CurrentId); Thread.Sleep(2000); if (!cltToken.IsCancellationRequested) { Console.WriteLine("End " + Task.CurrentId); } else { Console.WriteLine("Cancelled "+ Task.CurrentId); } } 

会产生类似于:

 Start 111 Start 112 Start 113 Start 114 Start 115 Start 116 Start 117 Start 118 Start 119 Start 120 Cancelled 111 Cancelled 112 Cancelled 118 Cancelled 116 Cancelled 114 Cancelled 113 Cancelled 117 Cancelled 115 Cancelled 119 Cancelled 120 

使用CancellationTokenSource是比中止线程更好的选择。 Thread.Abort是一个坏主意,因为它在没有提供适当的清理机制的情况下中止线程。 使用令牌可以让您以干净的方式协同处理取消。

至于为什么你的其他选项没有正常运作 – 你使用的时间有点太靠近了。 在调试器下运行时尤其如此,因为它会阻止时间(即: CancelAfterThread.Sleep )同时触发。 如果您在Visual Studio主机进程之外运行发布版本,您可能会发现它们的工作更加可靠。

首先,您所看到的未发出取消令牌信号的问题可能是由于细微的时序变化。 CancelAfter应该可以正常工作,但是你需要增加超时和睡眠之间的差异,以便更CancelAfter了解会发生什么。

其次,我可能是这里坏消息的预兆,但是如果这个非托管资源不能提供优雅地终止操作的机制,那么这只会指数地变得更难。 原因是:

  • 显然,当线程执行非托管代码时,无法轮询CancellationToken 。 因此,无法自行启动正常关机。
  • 除了你不应该中止一个线程这一事实之外, Thread.Abort调用不会将中止信号注入目标,直到它重新加入托管领域。 换句话说,中止不会终止正在执行非托管代码的线程。 这样做是为了让中止更安全。

使这种情况可靠地发生的唯一方法是在进程外运行非托管资源。 这意味着您需要启动一个新进程来执行非托管代码,然后使用WCF(或其他通信协议)来回发送消息/数据。 如果非托管资源未及时响应,则可以终止该进程。 这是安全的,因为杀死另一个进程不会破坏当前进程的状态。

希望您正在使用的任何非托管资源都有一个内置优雅终止机制的API。 如果它写得很好,它可能有这样的function,但我的经验表明许多人没有。