4

While "worker" is executing piece of code, I'm closing the whole window and I want to dispose it on closing that window because it is finishing it's code otherwise.

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

Unfortunetly, when I call worker.Dispose() in Close() there is an Exception:

A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled)

Any suggestions how I can dispose it while it is working?

Markiian Benovskyi
  • 2,137
  • 22
  • 29
Iavor Orlyov
  • 512
  • 1
  • 4
  • 15
  • 1
    You need to cancel the `Task` execution first. Read about task cancellation for details. – dymanoid Oct 29 '18 at 15:27
  • 2
    You don't. Cancel it first. This is why tasks accept `CancellationToken`s (and explicitly disposing a task is rarely necessary, or useful). – Jeroen Mostert Oct 29 '18 at 15:28
  • 2
    Stephan Toub wrote a nice blog post about the question: [Do I need to dispose of Tasks?](https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/) – SteMa Oct 29 '18 at 15:34
  • Thanks guys. Really helped! – Iavor Orlyov Oct 29 '18 at 18:35

2 Answers2

7

You need to write your code so that your task will accept a cancellation token. That's basically just a flag that can be checked by the code in your task, which if updated you would provide logic to handle, having your task's logic safely handle how to terminate its execution, rather than simply stopping at some unknown state. Running the below sample code in LinqPad should give you a reasonable example of what's going on:

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();
}

Key Points:

  • 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();
    

See https://binary-studio.com/2015/10/23/task-cancellation-in-c-and-things-you-should-know-about-it/ for a good write up / related ways to cancel a task.

Side Note

In the above example I was a bit lazy. Each iteration of the loop I check the cancellation token; but then (if not cancelled) wait 1 second before looping. Since the cancel logic only comes into play when the if statement is evaluated that means that we have to wait 1 second for the cancellation to take effect, which isn't great; if that delay was larger (e.g. 5 minutes), it could be really painful. One solution is outlined here: https://stackoverflow.com/a/17610886/361842

i.e. replace

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

with

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

See https://learn.microsoft.com/en-us/dotnet/api/system.threading.waithandle.waitone?view=netframework-4.7.2 for documentation on the WaitOne method.

JohnLBevan
  • 22,735
  • 13
  • 96
  • 178
-1

Try using a cancellation token.

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);

Keep the cancellationTokenSource handy and cancel it in your Close()

Minu
  • 232
  • 4
  • 13