0

I have a method called StartAutomation that executes a block of code inside a Task.Factory.StartNew(() => { }); to not block the UI while working on heavy loads. All is well until I started working on a way to cancel/abort a running Task at any point of time the user wishes to. I then followed this discussion on how I can abort a running Task and, for all that its worth, it works. It does cancel the running task with not errors and whatnot which I contained inside a separate method I then called StopAutomation.

However, after calling StopAutomation and deciding to give it another run by calling StartAutomation once more, the code inside Task.Factory.StartNew(() => { }); does not produce any result.

Below is my code snippet:

private Thread automationThread;

public void StartAutomation()
{
   Task.Factory.StartNew(() =>
   {
        automationThread = Thread.CurrentThread;

        try
        {
             Console.WriteLine("Starting process...");
        }
        catch (Exception e)
        {
             Console.WriteLine(e.Message);
        }
   });
}

public void StopAutomation()
{
   Console.WriteLine("Stopping...");

   automationThread.Abort();
}

Scenario:

  • User presses a button that calls StartAutomation(), code executes and a text is written on the screen.
  • User decides to cancel ongoing task. So they press a button that calls StopAutomation()
  • After a certain amount of time, User pressed the button that calls StartAutomation() again. The code no longer executes and no text is written on the screen.
Nii
  • 450
  • 6
  • 25
  • Why are you aborting the thread. This thread might be one of the ThreadPool? Don't terminate thread which you didn't spawn. – Jeroen van Langen Apr 06 '21 at 07:14
  • 1
    Your task finishes after writing to the Console. There is nothing that keeps your task running. – Jeroen van Langen Apr 06 '21 at 07:15
  • @JeroenvanLangen i'm trying to cancel the Task that runs the encapsulated block of code. – Nii Apr 06 '21 at 07:16
  • Your task is already finished, so there is nothing to cancel – Jeroen van Langen Apr 06 '21 at 07:17
  • @JeroenvanLangen the `Console.WriteLine` is just a placeholder. going forward, the Task will be holding a set of code that may take time to finish. – Nii Apr 06 '21 at 07:17
  • You should improve your question, if there is a loop inside or not. This task finishes directly, so it isn't a good example. – Jeroen van Langen Apr 06 '21 at 07:19
  • I'm sorry. For perspectives: what i'll be doing here is to fetch data from an external mysql server after a user defined time. By experience, that server has limited processing power and holds too much data so data extraction can take varying time to finish which ranges from 10~60 seconds. Naturally, i want to support cancelling of the request so they can change some parameters to better filter the results if the user wants to – Nii Apr 06 '21 at 07:24
  • Is there some reason that you can't use a CancellationToken? – SomeBody Apr 06 '21 at 07:24
  • You should never ever ever call `Thread.Abort()`. It's dangerous and can leave the run-time in an invalid state making all threads unreliable. – Enigmativity Apr 06 '21 at 08:21
  • Take a look at this: [What's wrong with using Thread.Abort()?](https://stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort) The worst part is that it's not even supported on .NET 5 and Core. Calling this method results to a `PlatformNotSupportedException`. – Theodor Zoulias Apr 06 '21 at 08:28

2 Answers2

2

Typically when working with long running Tasks you will be using the built in CancellationToken.

You can read a bit on how that works over here https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-cancel-a-task-and-its-children

In a nutshell, create a token like this:

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

You need to do this in the code where you're going to execute the code that runs within the Task.

Then, when you start your task, the code that is running the work can check anywhere if there is a cancellation request by checking token.IsCancellationRequested. You need to pass the token you created as an argument to the code that is running your work.

Outside of the task's content, you can then call tokenSource.Cancel(). After calling that, the isCancellationRequested will yield true the next time it is checked.

As a sidenote: you're executing automationThread.Abort() after setting it to Thread.CurrentThread. This is quite dangerous as you were not responsible for creating that thread, it might as well be the thread running your application. Tasks are not Threads, Tasks run within threads.

Glubus
  • 2,819
  • 1
  • 12
  • 26
  • Upvoted. My only disagreement is with the `IsCancellationRequested`. This property should be used sparingly. The standard way to communicate cancellation is by throwing an `OperationCanceledException`, so call the `ThrowIfCancellationRequested` method instead. – Theodor Zoulias Apr 06 '21 at 08:34
  • Fair enough, tbh I am not THAT familiar with the best practices on this subject, so today I learned as well! – Glubus Apr 06 '21 at 08:57
  • Glubus you could take a look at this tutorial: [Cancellation in Managed Threads](https://learn.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads). Surprisingly to me, it features the use of the `IsCancellationRequested` property in an example about polling during long-running computations, so the best practices in this case may not be written on stone. – Theodor Zoulias Apr 06 '21 at 09:12
1

Here's what your code might have looked like to avoid mucking around with threads.

async Task Main()
{
    cts = new CancellationTokenSource();

    StartAutomation();
    await Task.Delay(TimeSpan.FromSeconds(2.0));
    StopAutomation();
}

private Task task;
private CancellationTokenSource cts;

public void StartAutomation()
{
    task = Task.Run(() =>
    {
        try
        {
            Console.WriteLine("Starting task...");
            while (true)
            {
                //Do something
                if (cts.Token.IsCancellationRequested)
                {
                    Console.WriteLine("Ending task...");
                    return;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }, cts.Token);
}

public void StopAutomation()
{
    Console.WriteLine("Stopping...");
    cts.Cancel();
}

This outputs:

Starting task...
Stopping...
Ending task...
Enigmativity
  • 113,464
  • 11
  • 89
  • 172