Cancel task by time

天涯浪子 提交于 2019-12-09 03:34:07

问题


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:

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);
        }
    }

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);
        }
    }

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???


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/19323191/cancel-task-by-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!