Is there a proper way to cancel an asynchronous task?

社会主义新天地 提交于 2020-02-06 04:27:05

问题


I encountered a problem how to properly cancel an asynchronous task.

Here is some draft.

My entry point runs two asynchronous task. The first task does some 'long' work and the second one cancels it.

Entry point:

private static void Main()
{
    var ctc = new CancellationTokenSource();

    var cancellable = ExecuteLongCancellableMethod(ctc.Token);

    var cancelationTask = Task.Run(() =>
    {
        Thread.Sleep(2000);

        Console.WriteLine("[Before cancellation]");

        ctc.Cancel();
    });

    try
    {
        Task.WaitAll(cancellable, cancelationTask);
    }
    catch (Exception e)
    {
        Console.WriteLine($"An exception occurred with type {e.GetType().Name}");
    }
}

Method that returns cancel-able task:

private static Task ExecuteLongCancellableMethod(CancellationToken token)
{
    return Task.Run(() =>
    {
        token.ThrowIfCancellationRequested();

        Console.WriteLine("1st"); 
        Thread.Sleep(1000);

        Console.WriteLine("2nd");
        Thread.Sleep(1000);

        Console.WriteLine("3rd");
        Thread.Sleep(1000);

        Console.WriteLine("4th");
        Thread.Sleep(1000);

        Console.WriteLine("[Completed]");

    }, token);  
}   

My purpose is to stop writing '1st','2nd','3rd' immediately after cancellation is called. But I get following results:

1st
2nd
3rd
[Before cancellation]
4th
[Completed]

For obvious reason I didn't get an exception that throws when cancellation is requested. So I tried to rewrite method as following:

private static Task ExecuteLongCancellableAdvancedMethod(CancellationToken token)
{
    return Task.Run(() =>
    {
        var actions = new List<Action>
        {
            () => Console.WriteLine("1st"),
            () => Console.WriteLine("2nd"),
            () => Console.WriteLine("3rd"),
            () => Console.WriteLine("4th"),
            () => Console.WriteLine("[Completed]")
        };

        foreach (var action in actions)
        {
            token.ThrowIfCancellationRequested();

            action.Invoke();

            Thread.Sleep(1000);
        }

    }, token);
}

And now I got what I want:

1st
2nd
[Before cancellation]
3rd
An exception occurred with type AggregateException

But I guess creating of a collection of Action delegates and looping through it is not the most convenient way to deal with my problem.

So what's the proper way to do it? And why do I need to pass my cancellation token into Task.Run method as the second argument?


回答1:


The Task won't cancel its self, it is up you you to detect the cancellation request and cleanly abort your work. That's what token.ThrowIfCancellationRequested(); does.

You should place those checks throughout your code, in places where execution can be cleanly stopped, or rolled back to a safe state.

In your second example, you call it once per iteration of the loop, and it works fine. The first example only calls it once, at the very start. If the token hasn't been canceled by that point, the task will run to completion, just like you are seeing.

If you changed it to look like this, you would also see the results you expect.

return Task.Run(() =>
{
    token.ThrowIfCancellationRequested();
    Console.WriteLine("1st"); 
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("2nd");
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("3rd");
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("4th");
    Thread.Sleep(1000);

    Console.WriteLine("[Completed]");

}, token); 


来源:https://stackoverflow.com/questions/45946345/is-there-a-proper-way-to-cancel-an-asynchronous-task

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