Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# - Trying to Dispose task while it is executing code

Tags:

c#

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?

like image 865
Iavor Orlyov Avatar asked Sep 19 '25 18:09

Iavor Orlyov


1 Answers

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.

like image 81
JohnLBevan Avatar answered Sep 22 '25 09:09

JohnLBevan