C# - 尝试在任务执行时Dispose任务对象

4

当"worker"正在执行一段代码时,我关闭了整个窗口,希望在关闭该窗口时将其处理掉,因为它正在完成其代码。

Task  worker = Task.Factory.StartNew(new Action(() =>
{ 
    // some code here
}

不幸的是,当我在Close()中调用worker.Dispose()时,会出现异常:

只有处于完成状态(RanToCompletion、Faulted或Canceled)的任务才能被处理

有什么建议可以在它工作时处理它?


1
你需要先取消Task的执行。请阅读有关任务取消的详细信息。 - dymanoid
2
你不需要这样做。首先取消它。这就是为什么任务接受“CancellationToken”(显式处理任务很少是必要的或有用的)。 - Jeroen Mostert
2
Stephan Toub 写了一篇关于问题的好博客文章:我需要处理 Tasks 吗? - SteMa
谢谢大家。真的帮了我很多! - Iavor Orlyov
2个回答

7

您需要编写代码,以便您的任务将接受一个取消令牌。这基本上只是一个标记,可以由任务中的代码检查,如果更新,您将提供处理逻辑,使任务的逻辑安全地处理如何终止其执行,而不是仅在某个未知状态下停止。 在LinqPad中运行下面的示例代码应该可以给您一个合理的示例:

void Main()
{
    var form = new Form();
    var label = new Label(){Text = string.Format("{0:HH:mm:ss}", DateTime.UtcNow), AutoSize = true};
    form.Controls.Add(label);
    var taskController = new CancellationTokenSource(); 
    var token = taskController.Token;
    var task = Task.Run(() => 
    {
        for (var i=0; i<100; i++)
        {
            var text = string.Format("{0:HH:mm:ss}", DateTime.UtcNow);
            Console.WriteLine(text); //lets us see what the task does after the form's closed
            label.Text = text;
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Cancellation Token Detected");
                break;
            }
            Thread.Sleep(1000);
        }
    }, token);
    form.FormClosed += new FormClosedEventHandler(
        (object sender, FormClosedEventArgs e) => 
        {taskController.Cancel();}
    );
    form.Show();
}

重点:

  • Create an instance of CancellationTokenSource. This is a simple object which will allow you to communicate when you wish to cancel to your task.

    var taskController = new CancellationTokenSource(); 
    
  • Fetch the token from this source

    var token = taskController.Token;
    
  • Run the task, passing a reference to the token

    var task = Task.Run(() => 
    {
        //...
        , token
    }
    
  • Add logic within the task to check the status of this token at suitable points, & handle it appropriately.

    if (token.IsCancellationRequested)
    {
        Console.WriteLine("Cancellation Token Detected");
        break;
    }
    
  • Add logic to call the Cancel method when you wish to cancel the task. In the above code I've put this under the Form's FormClosed event handler's logic:

    taskController.Cancel();
    
请参考以下链接了解有关C#中任务取消的相关信息:https://binary-studio.com/2015/10/23/task-cancellation-in-c-and-things-you-should-know-about-it/。在上面的示例中,我有点懒惰。每次循环迭代时,我都会检查取消标记;但然后(如果未取消)等待1秒再进行下一轮循环。由于取消逻辑仅在评估if语句时起作用,这意味着我们必须等待1秒才能使取消生效,这并不好;如果该延迟更长(例如5分钟),那将会非常痛苦。可以在此处找到一种解决方案:https://stackoverflow.com/a/17610886/361842。即替换掉:
if (token.IsCancellationRequested)
{
    Console.WriteLine("Cancellation Token Detected");
    break;
}
Thread.Sleep(1000);

使用

if (token.IsCancellationRequested)
{
    Console.WriteLine("Cancellation Token Detected");
    break;
}
token.WaitHandle.WaitOne(1000);

请参考 https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.waithandle.waitone?view=netframework-4.7.2 了解有关 WaitOne 方法的文档。

1
太好了!我非常感激! - Iavor Orlyov
你可以使用 await Task.Delay(1000, token); 代替这个 WaitHandle 的东西。 - Endrju

-1

尝试使用取消令牌。

var cancellationTokenSource = new CancellationTokenSource();
var t = Task.Factory.StartNew(() =>
        {
            // Your code here 
        }, cancellationTokenSource.Token).ContinueWith(task =>
          {
              if (!task.IsCompleted || task.IsFaulted)
              {
                  // log error
              }
          }, cancellationTokenSource.Token);

保持 cancellationTokenSource 方便,并在 Close() 中取消它。


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