【问题标题】:how to cancel Task quicker如何更快地取消任务
【发布时间】:2013-07-11 15:34:27
【问题描述】:

我有一个在启动时启动任务的 Windows 服务

这个任务有一个 while 循环,在执行一次迭代后它会休眠 5 分钟。

当我停止服务时,首先取消任务,然后执行一些其他操作

如果任务处于睡眠状态,它只会在它醒来时被取消,我希望它被取消,即使它正在睡眠并且不想等待唤醒它。

下面是代码

Task controllerTask = Task.Factory.StartNew(() =>
{
    var interval = 300;
    while(true)
    {
        if (cancellationToken.IsCancellationRequested) 
            break;
        Thread.Sleep(interval * 1000);
        if (cancellationToken.IsCancellationRequested) 
            break;
        //SOME WORK HERE
    }
}, cancellationToken);

有什么办法吗?

编辑: 我无法使用 Task.Delay ,在 System.Threading.Tasks.Task 命名空间中找不到它,因为我使用的是 .Net Framework 4.0 而不是 4.5

还有其他更好的解决方案适用于 4.0。

【问题讨论】:

    标签: c# multithreading .net-4.0


    【解决方案1】:

    使用Task.Delay 代替Thread.Sleep。它需要一个CancellationToken 参数,因此您可以在延迟结束之前中止它。

    如果你使用的是异步代码,你可以这样写:

    await Task.Delay(duration, cancellationToken);
    

    如果是同步代码,你可以等待任务:

    Task.Delay(duration, cancellationToken).Wait();
    

    【讨论】:

    • 这还有一个好处是它不会在 5 分钟内占用线程池线程(在第一个异步情况下)。
    • @DanBryant 你是说await 不会绑定它,而.Wait() 可以吗?或者只是 OP 把它绑起来了。
    • @Thomas 你能在我的代码上写下解决方案吗,把这条线放在哪里?代替 Thread.Sleep?
    • @ImranRizvi、@ThomasLevesque:您可以从 Microsoft.Bcl.Async NuGet 库获得对 .NET 4 的 async 支持。它包括一个TaskEx.Delay 方法。
    • @StephenCleary,是的,但您仍然需要 VS2012(或至少 C# 5 编译器)
    【解决方案2】:

    这是您可以在 C# 4.0 VS2010 中使用的一种阻塞解决方案。

    cancellationToken.WaitHandle.WaitOne(TimeSpan.FromMinutes(5));
    

    当您取消令牌源或超时(这是您想要的睡眠间隔)时,它将解除阻塞。

    【讨论】:

      【解决方案3】:

      受其他答案的启发,使用 await 解决此问题的简单示例:

      public static class TaskExtension
      {
          /// <summary>
          /// Call to highlight fact that you do not want to wait on this task.
          ///
          /// This nicely removes resharper warnings without need for comments.
          /// </summary>
          /// <param name="task"></param>
          public static void FireAndForget(this Task task)
          {
          }
      }
      
      internal class Program
      {
          private static void Main(string[] args)
          {
              var cancellationToken = new CancellationTokenSource();
              TaskCode(cancellationToken.Token).FireAndForget();
              Console.ReadLine();
              cancellationToken.Cancel();
              Console.WriteLine("End");
              Console.ReadLine();
          }
      
          private static async Task TaskCode(CancellationToken cancellationToken)
          {
              while (!cancellationToken.IsCancellationRequested)
              {
                  var interval = TimeSpan.FromSeconds(1);
                  await Task.Delay(interval, cancellationToken);
      
                  //SOME WORK HERE
                  Console.WriteLine("Tick");
              }
          }
      }
      

      【讨论】:

      • 我建议让 TaskCode 返回 Task 而不是 void。 void 返回实际上只是为了向后兼容,以便可以使事件处理程序委托异步而不更改其返回值。允许 TaskCanceledException 传播而不是将其更改为空的 return 也是一个好主意,因为这样最终的 Task 将正确地具有 Canceled 状态。
      • 其实你不需要使用Task.StartNew,因为TaskCode会在遇到第一个await时返回给调用者。然后它将在Delay 结束时恢复执行。顺便说一句,您不应该通过CancellationTokenSource,而应该通过CancellationToken
      • 感谢您的回答@weston,但我无法使用 Task.Delay ,我在 System.Threading.Tasks.Task 命名空间中找不到它,因为我使用的是 .Net Framework 4.0不是 4.5
      • @DanBryant 谢谢,我发现了异常,因为它是 void 返回类型,所以修复返回类型就不需要了。
      • @ThomasLevesque 喜欢吗?请注意,我收到一个 resharper 错误警告我我没有等待 TaskCode 的结果,但如果没有更好的方法,我可以用评论忽略它。
      【解决方案4】:

      我已将长睡眠分解为多个小睡眠,以下是修改后的代码:

      Task controllerTask = Task.Factory.StartNew(() =>
      {
       while(true)
       {
           if (cancellationToken.IsCancellationRequested) break;
           var sleepTime = 10;
           if (interval < sleepTime)
               interval = sleepTime;
      
           var iteration = (interval / sleepTime);
           if ((interval % sleepTime) > 0)
               iteration++;
           bool cancel = false;
           for (int i = 0; i < iteration; i++)
           {
               Thread.Sleep(sleepTime * 1000);
               if (cancellationToken.IsCancellationRequested) { cancel = true; break; };
           }
           if (cancel) break;
      
           //SOME WORK HERE
       }
      }
      

      【讨论】:

      • 我建议使用cancellationToken 采取更好的解决方案。这种方法是在浪费周期。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-27
      • 2018-09-05
      • 2018-12-10
      • 1970-01-01
      • 2015-12-20
      • 1970-01-01
      相关资源
      最近更新 更多