1

I am trying to stop a task in C# after a certain period of time.

For my code: the Task.Delay().Wait() should just represent some work that the Task does.

My Code:

public static void Main()
{
    Console.WriteLine("starting app");
    try
    {
        Console.WriteLine("before");
        DoStuff(1000);
        Console.WriteLine("after");
    }
    catch
    {
        Console.WriteLine("TIMEOUT");
    }
    Console.WriteLine("Main finished wait 5 sec now");
    Task.Delay(10000).Wait();
    Console.WriteLine("Closing app now");
}

public static async Task DoStuff(int time)
{
    Task real = Task.Run(()=>
    {
        Task.Delay(2000).Wait();
        Console.WriteLine("Task Running");
        Task.Delay(2000).Wait();
        Console.WriteLine("still running");
        Task.Delay(2000).Wait();
        Console.WriteLine("Not dead yet");
        Task.Delay(1000).Wait();
        Console.WriteLine("Task done");
        Task.Delay(5000);
    });

    bool success = real.Wait(time);
    if ( success )
    {
        Console.WriteLine("Task finished in time");
        await real;
    }
    else
    {
        Console.WriteLine("Task did not finish");
        real.Dispose();
        throw new TimeoutException("The task took too long");
    }
}

I already tried it with the cancellation token, but I do not have a loop to check the token every iteration. So I tried to do it with the Task.Wait(time) and I get the right message, but the task does not stop after using the Task.Dispose() method. I get the following output:

enter image description here

So I get the current output but the task continues to run in the back.. Any ideas on how to solve this?

  • 1
    It is very wrong to call `Wait` on all the tasks in `DoStuff` – Ackdari Jan 27 '21 at 08:28
  • _"How to stop asyn Task in C# after time"_ use a [CancellationToken](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.-ctor?view=net-5.0#System_Threading_CancellationTokenSource__ctor_System_TimeSpan_) with timeout. – Fildor Jan 27 '21 at 08:35
  • There is an `Task.Delay(5000);` in the question that is neither waited or awaited, so it does nothing. Could you fix it? Btw when we want to simulate long running synchronous work, we usually write `Thread.Sleep(5000)` instead of `Task.Delay(5000).Wait()`. – Theodor Zoulias Jan 27 '21 at 08:43
  • 1
    @TheodorZoulias given that every `.Wait()` is *also* horribly broken, I'm not sure that is worth too much special attention :) – Marc Gravell Jan 27 '21 at 08:45
  • 1
    @MarcGravell "horribly" is a strong word. I would call it "not optimal". – Theodor Zoulias Jan 27 '21 at 08:47
  • @TheodorZoulias and I would strongly disagree; it is much worse than "not optimal" - it can actively deadlock a thread (possibly two threads), depending on the ambient environment (in particular, what the sync-context is) – Marc Gravell Jan 27 '21 at 08:49
  • @MarcGravell well, deadlocking a thread with `Wait` is one of the most benign multithreading problems a programmer may face. It is a problem that makes itself apparent instantly, and demand the programmer's immediate attention. It's nothing like a Heisenbug that appears every blue moon. – Theodor Zoulias Jan 27 '21 at 08:58
  • 1
    Alex so if I understand correctly you want to cancel the `DoStuff` asynchronous method in a non-cooperative fashion? If that's the case, then the short answer is "you can't". Cancellation in .NET is [cooperative](https://docs.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads). Non-cooperative cancellation is only possible by [aborting threads](https://stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort), which is dangerous because it can corrupt the internal state of your program, and it's not even supported in the newer versions of .NET. – Theodor Zoulias Jan 27 '21 at 09:46
  • So what I want to do is I am trying to do something in the DoStuff Method, this could take a very long time, but I do not want to wait for ever, I want the new Task to stop either if it is finished or after a certain time of not completing. I am new to async programming and I am sorry that you are supposed to use Thread.Sleep() instead of Task.Delay().Wait(), I did not know that, thanks! – Alex IsMyName Jan 27 '21 at 13:11
  • 1
    You can't really cancel a `Task` that is not designed to be cancelled. What you can do is to cancel the awaiting of the task. In other words the task will keep running, but you'll just ignore it, and your code will not wait it to complete. If you are OK with that, you can check out this [question](https://stackoverflow.com/questions/10134310/how-to-cancel-a-task-in-await) for solutions. Someone posted a handy `WaitOrCancel` method that you could use. – Theodor Zoulias Jan 28 '21 at 01:08

1 Answers1

2

Firstly: never Wait() on tasks (unless you know they have already finished); use await. As for the timeout: CancellationTokenSource can be made to time out after a delay, as below.

Note that cancellation-tokens do not interrupt code - you (or other code, as in the case of Task.Delay) need to either check for cancellation (for example, ThrowIfCancellationRequested()), or you need to use Register(...) to add a callback that will be invoked when it is cancelled.

using System;
using System.Threading;
using System.Threading.Tasks;

static class P
{

    public static async Task Main()
    {
        Console.WriteLine("starting app");
        try
        {

            Console.WriteLine("before");
            await DoStuffAsync(1000);
            Console.WriteLine("after");
        }
        catch
        {
            Console.WriteLine("TIMEOUT");
        }
        Console.WriteLine("Main finished wait 5 sec now");
        await Task.Delay(5000);
        Console.WriteLine("Closing app now");
    }
    public static async Task DoStuffAsync(int time)
    {
        using var cts = new CancellationTokenSource(time); // assuming here that "time" is milliseconds
        Task real = Task.Run(async () =>
        {
            await Task.Delay(2000, cts.Token);
            Console.WriteLine("Task Running");
            await Task.Delay(2000, cts.Token);
            Console.WriteLine("still running");
            await Task.Delay(2000, cts.Token);
            Console.WriteLine("Not dead yet");
            await Task.Delay(2000, cts.Token);
            Console.WriteLine("Task done");
            await Task.Delay(2000, cts.Token);
        });

        bool success;
        try
        {
            await real;
            success = true;
        }
        catch (OperationCanceledException)
        {
            success = false;
        }

        if (success)
        {
            Console.WriteLine("Task finished in time");
        }
        else
        {
            Console.WriteLine("Task tool too long");
        }
    }
}

Note that you can also pass a cancellation-token into Task.Run, but that simply gets checked before executing the callback - and there isn't usually a significant delay on that, so... it isn't usually worth it.

Additional note: if you want to be sure exactly what was cancelled (i.e. is the OperationCanceledException coming from cts):

catch (OperationCanceledException cancel) when (cancel.CancellationToken == cts.Token)
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    Just a note to OP: I personally prefer the ctor of CancellationTokenSource with `TimeSpan` for readability. But that's irrelevant to the functionality. – Fildor Jan 27 '21 at 09:46
  • As you said, the cancellation token will be checked before executing the Task.Run, but I want to cancle the Task while it is running and not before starting. I think the registration is the way to go for this problem, right? – Alex IsMyName Jan 27 '21 at 13:15
  • @AlexIsMyName there was nothing shown in the question that was suitable for `Register()`. As I said: it is up to *your* code to check for cancellation, unless you can piggy-pack off inbuilt checks (like in `Task.Delay(...)`) – Marc Gravell Jan 27 '21 at 16:04