0

I have code like below. Simply run the while loop for a long time.

public void Test(){
    var task1 = Task.Run(() =>
    {
        while(true){
            // forever
        }
    }
}

When Test() is called, task1 will be executed. Then, when I run Test() again immediately, two tasks exist at the same time.

Is there a way to cancel the task that was already running when a new task is created?

Edit for Michał Turczyn

    int i = 0;
    private CancellationTokenSource cancellationTokenSource;

    public void Test(int b)
    {
        if (cancellationTokenSource != null)
        {
            // Cancel previously created task.
            cancellationTokenSource.Cancel();
        }

        cancellationTokenSource = new CancellationTokenSource();

        var task1 = Task.Run(() =>
        {
            while (!cancellationTokenSource.IsCancellationRequested)
            {
                Console.WriteLine(b);
                // forever
            }
        });
    }

    public void button_pushed(){
        Console.WriteLine("pushed");
        Test(i); i++; return;
    }

The log is like:

pushed
0
0
0
0
0
pushed
1
1
1
1
pushed
1
1
1
1
2
2
2
2
2
1
1
1
1
2
2
2
pushed
1
1
1
1
2
2
2
2
2
1
1
1
1
3
3
3
3
2
2
2

....

It looks like past tasks are still running. But when I executed it three times, the first output (0) disappeared, so is this a timing issue?

What I looking for is like:

pushed
0
0
0
0
pushed
1
1
1
1
pushed
2
2
2
2

....
5
  • 2
    Why not have task1 as a field / property instead of local variable? Commented Aug 22, 2024 at 10:00
  • 1
    Pass a CancellationToken to the method., then change to while (!CancellationToken.IsCancelationRequested). Commented Aug 22, 2024 at 10:01
  • 4
    Do you really want to cancel the first run or would you rather have the second call not start a second one? As-is it seems arbitrary which of the two is running, as long as there is one running. Commented Aug 22, 2024 at 10:03
  • 3
    I'd be tempted to take a step back and ask if this is a good solution in the first place? For example, might you be better off with a BackgroundService? Commented Aug 22, 2024 at 10:17
  • If I understand correctly the comment // forever represents some code that you want to run periodically. It is important to clarify what do you want to happen when the button_pushed event occurs. Do you want 1. To wait peacefully for the completion of the // forever code before launching the new task or 2. Abort/kill/terminate immediately and forcefully the // forever code and then launch the new task? Commented Aug 22, 2024 at 13:08

1 Answer 1

3

You can try using CancellationTokenSource. when the method is invoked (and Task is created, pass to it token for CancellationTokenSource, which also will be created for the task. Before creatoin of the CancellationTokenSource, we just need to check if it was created and if so, call Cancel method.

Simple implementation:

private CancellationTokenSource cancellationTokenSource;

public void Test()
{
    if(cancellationTokenSource != null)
    {
        // Cancel previously created task.
        cancellationTokenSource.Cancel();
    }

    cancellationTokenSource = new();

    var task1 = Task.Run(() =>
    {
        while(!cancellationTokenSource.IsCancellationRequested){
            // forever
        }
    });
}

Of course, if you want, you can keep track of all CancellationTokenSources in some collection, make it thread safe by using ConcurrentBag to store them, etc. This is just simplified idea.

UPDATE

After reading your update with my solution, I decided to give more detailed implementation, with storing all cancellatio token sources and cancelling them properly:

public class Test
{
    int i = 0;
    private ConcurrentBag<CancellationTokenSource> cancellationTokenSources = new();

    public void Test(int b)
    {
        var notCancelled = cancellationTokenSources
            .Where(c => !c.IsCancellationRequested)
            .ToArray();
        // Cancel previously created tasks.
        Array.ForEach(notCancelled, x => x.Cancel());

        var cts = new CancellationTokenSource();
        cancellationTokenSources.Add(cts);

        var task1 = Task.Run(() =>
        {
            while (!cts.IsCancellationRequested)
            {
                Console.WriteLine(b);
                // forever
            }
            Console.WriteLine("FININSHED");
        });
    }

    public void button_pushed()
    {
        Console.WriteLine("pushed");
        Test(i); i++; return;
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Passing the Token to a Task only cancels it, if it has not yet started, so it doesn't work with Task.Run because it starts immediately. You must check the Token manually.
while(true){ should check token state, right? (Did not DV)
To clarify: Passing a Tokento Task.Run only works if the token is already cancelled when you call Task.Run. In that case the Task never starts.
Thank you for your answer. I tried your code, but it seems that past tasks are still running. I've edited the question and included more details. I would be happy if you could teach me when you have time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.