Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cancel task by time

I have a multi-threaded application where I need to cancel each task after a certain time, even if at the time of cancellation, they use unmanaged resources. Now I use the following code (for example, a console application). In a real application, the delay may occur in the unmanaged resource.

static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            Task.Factory.StartNew(Do, TaskCreationOptions.LongRunning);
        }

        Console.ReadLine();
    }

    private static void Do()
    {
        new Timer(Thread.CurrentThread.Abort, null, 1000, -1);

        try
        {
            Console.WriteLine("Start " + Task.CurrentId);
            Thread.Sleep(2000);
            Console.WriteLine("End " + Task.CurrentId);
        }
        catch (Exception)
        {
            Console.WriteLine("Thread Aborted " + Task.CurrentId);
        }
    }

Get the result:

enter image description here

But I'm not sure whether it is right for a real application from the point of view of safety. I also used CancellationToken in different variants, but it not give me correct result because where i use CancellAfter() or .Delay() with timespan and cancel task after ceratain time I got the following results:

static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            var clt = new CancellationTokenSource();

            Task task = new Task(() =>
            {
                Task.Delay(2000).ContinueWith(_ =>
                {
                    clt.Cancel();

                }, clt.Token);

                Do(clt.Token);

            }, clt.Token);

            task.Start();
        }

        Console.ReadLine();
    }

    private static void Do(CancellationToken cltToken)
    {
        Console.WriteLine("Start " + Task.CurrentId);

        Thread.Sleep(2500);

        if (!cltToken.IsCancellationRequested)
        {
            Console.WriteLine("End " + Task.CurrentId);
        }
        else
        {
            Console.WriteLine("Cancelled "+ Task.CurrentId);
        }
    }

enter image description here

In this situation all task must be cancelled, because Thread.Sleep() > of time allotted to execute each task. But we can see that some of the time to execute.

I also use following construction and give the same result:

        static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            var clt = new CancellationTokenSource();
            clt.CancelAfter(2000);

            Task.Factory.StartNew(Do, clt.Token);

        }

        Console.ReadLine();
    }

    private static void Do(object obj)
    {
        var cltToken = (CancellationToken) obj;

        Console.WriteLine("Start " + Task.CurrentId);

        Thread.Sleep(2500);

        if (!cltToken.IsCancellationRequested)
        {
            Console.WriteLine("End " + Task.CurrentId);
        }
        else
        {
            Console.WriteLine("Cancelled "+ Task.CurrentId);
        }
    }

enter image description here

I also use Parallel and initialize Cancellation Token Inside method Do(), and use Timer to cancell token after timespan, but all give same result.

SO, Why is this happening and what is the correct way to cancel the task after a certain time???

like image 851
Andrey Zhminka Avatar asked Oct 11 '13 16:10

Andrey Zhminka


People also ask

How do I cancel async tasks?

You can cancel an asynchronous operation after a period of time by using the CancellationTokenSource. CancelAfter method if you don't want to wait for the operation to finish.

What is difference between CancellationTokenSource and CancellationToken?

CancellationTokenSource – an object responsible for creating a cancellation token and sending a cancellation request to all copies of that token. CancellationToken – a structure used by listeners to monitor token current state.

What is a CancellationToken?

A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects. You create a cancellation token by instantiating a CancellationTokenSource object, which manages cancellation tokens retrieved from its CancellationTokenSource.


2 Answers

You can get the same results as your original "abort" version by using the same timings. For example, this code:

static void Main()
{
    var clt = new CancellationTokenSource();
    clt.CancelAfter(1000);
    for (int i = 0; i < 10; i++)
    {
        Task.Run(() => Do(clt.Token));
    }
    Console.ReadLine();
}

private static void Do(CancellationToken cltToken)
{
    Console.WriteLine("Start " + Task.CurrentId);
    Thread.Sleep(2000);

    if (!cltToken.IsCancellationRequested)
    {
        Console.WriteLine("End " + Task.CurrentId);
    }
    else
    {
        Console.WriteLine("Cancelled "+ Task.CurrentId);
    }
}

Will produce something simliar to:

Start 111
Start 112
Start 113
Start 114
Start 115
Start 116
Start 117
Start 118
Start 119
Start 120
Cancelled 111
Cancelled 112
Cancelled 118
Cancelled 116
Cancelled 114
Cancelled 113
Cancelled 117
Cancelled 115
Cancelled 119
Cancelled 120

Using a CancellationTokenSource is a better option than aborting threads. Thread.Abort is a bad idea since it aborts the thread without providing proper cleanup mechanisms. Using the token allows you to cooperatively handle the cancellation in a clean manner.

As for why your other options were not functioning properly - The timings you used were a bit too close together. This is especially an issue when running under the debugger, as it will prevent the timings (ie: CancelAfter as well as Thread.Sleep) from firing at the same time. If you run a release build outside of the Visual Studio host process, you'll likely find that they work far more reliably.

like image 145
Reed Copsey Avatar answered Sep 23 '22 14:09

Reed Copsey


First, the issue you are seeing where the cancellation token is not signaled is probably due to subtle timing variations. CancelAfter should work fine, but you will need to increase the difference between the timeout and the sleep to get a more realistic of picture of what will happen.

Second, and I may be the harbinger of bad news here, but if this unmanaged resource does not offer mechanisms for gracefully terminating an operation then this just got exponentially harder. The reasons are:

  • Obviously there is no way to poll the CancellationToken while a thread is executing unmanaged code. So there is no way to initiate a gracefully shutdown on your own.
  • Aside from the fact that you should not be aborting a thread anyway the Thread.Abort call will not inject the abort signal into the target until it rejoins the managed realm. In other words, aborts will not terminate threads that are executing unmanaged code. This was done intentionally to make aborts safer.

The only way to make this happen reliably is to run the unmanaged resource out-of-process. That means you will need to spin up a new process to execute the unmanaged code and then use WCF (or other communication protocol) to send messages/data back and forth. If the unmanaged resource does not respond in a timely manner then you can kill the process. This is safe because killing another process does not corrupt the state of the current process.

Hopefully whatever unmanaged resource it is you are using has an API with a gracefully termination mechanism built into it. If it is well written it might have such a feature, but my experience has shown that many do not.

like image 20
Brian Gideon Avatar answered Sep 20 '22 14:09

Brian Gideon