简体   繁体   English

取消并等待异步任务方法

[英]Cancel and wait on async Task methods

Trying to figure this out but can't find anything definitive.试图弄清楚这一点,但找不到任何确定的东西。

Suppose I have a class that does some work async and can be started or stopped on the fly:假设我有一个 class 可以异步执行一些工作并且可以即时启动或停止:

class ModelObject
{
    private CancellationTokenSource cts;
    private CancellationToken ct;
    private Progress<int> progress;
    
    private async Task DoSomething(IProgress<int> progress)
    {
        ...
        
        while (DoingSomething)
        {
            if (ct.IsCancellationRequested)
            {
                await Task.Delay(10000); // Take our sweet time to cancel
                // Cancel work and return
            }
            
            ...
        }       
    }
    
    public async void StartAsync()
    {
        progress = new Progress<int>(...);
        cts = new CancellationTokenSource();
        ct = cts.Token;
        
        await DoSomething(progress);
    }
    
    public void Stop()
    {
        ct.Cancel();
        
        // HELP: await while DoSomething cancels
        
        // perform cleanup
    }
}

I am not sure how to await while the existing instance of DoSomething cancels.当现有的DoSomething实例取消时,我不确定如何await

Typical use would be like this:典型的用法是这样的:

ModelObject modelObject = new ModelObject();
modelObject.StartAsync();
modelObject.Stop();

Any help would be highly appreciated.任何帮助将不胜感激。

While you could await the task returned from DoSomething , you can also use a TaskCompletionSource ;虽然您可以等待从DoSomething返回的任务,但您也可以使用TaskCompletionSource

        private TaskCompletionSource<object> tcs;

        private async Task DoSomething(IProgress<int> progress){
          ...
           while (DoingSomething)
           {
               if (ct.IsCancellationRequested)
               {
                   // Cancel work and return
               } 
            ...
           }       
           tcs.SetResult(null);
        }

        public async Task Stop () {
           cts.Cancel();
           await tcs.Task;
        }

My preference would be to add a Completion property to the ModelObject class.我的偏好是将Completion属性添加到ModelObject class。 The caller could choose to await or not the Completion after canceling an operation.调用者可以在取消操作后选择await或不等待Completion Also I would probably place guards against an improper use of the API.此外,我可能会防范不当使用 API。 Attempting to start a new operation while an existing one is pending, would cause an exception.在现有操作挂起时尝试启动新操作会导致异常。

public Task Completion { get; private set; } = Task.CompletedTask;

public async void Start()
{
    if (_cts != null) throw new InvalidOperationException("Operation is in progress.");
    using (_cts = new CancellationTokenSource())
    {
        _ct = _cts.Token;
        _progress = new Progress<int>(/*...*/);
        this.Completion = DoSomethingAsync();
        try
        {
            await this.Completion;
        }
        catch { } // Ignore
        finally
        {
            _cts = null;
            _ct = default;
            _progress = null;
        }
    }
}

public void Stop()
{
    _cts?.Cancel();
}

Another piece of advice: in case of cancellation ensure that your DoSomethingAsync throws an OperationCanceledException .另一条建议:如果取消,请确保您的DoSomethingAsync抛出OperationCanceledException This is the expected behavior of a cancelable asynchronous operation.这是可取消异步操作的预期行为 For example if you pass the token to any built-in API, you'll notice that the returned task will complete as Canceled immediately after calling the _cts.Cancel() method.例如,如果您将令牌传递给任何内置的 API,您会注意到返回的任务将在调用_cts.Cancel()方法后立即以Canceled的形式完成。 To mimic this behavior you could call _ct.ThrowIfCancellationRequested() inside the loop, and pass the token to any API that accepts it (for example the Task.Delay method).为了模仿这种行为,您可以在循环内调用_ct.ThrowIfCancellationRequested() ,并将令牌传递给任何接受它的 API(例如Task.Delay方法)。 In fact it would be probably better to catch internally this exception, and do the clean up inside the catch block:事实上,在内部捕获这个异常可能会更好,并在 catch 块内进行清理:

private async Task DoSomethingAsync()
{
    try
    {
        while (doingSomething)
        {
            // Do some work
            _ct.ThrowIfCancellationRequested();
        }
    }
    catch (OperationCanceledException)
    {
        await Task.Delay(10000); // Take our sweet time to cancel
        throw; // Rethrow the exception
    }
    finally
    {
        // perform cleanup, whether successful, failed or canceled
    }
}

I'm not sure why StartAsync method is async since it indefinitely executes unless the Stop is Invoked.我不确定为什么StartAsync方法是异步的,因为除非调用Stop ,否则它会无限期地执行。 I mean it will not return at some point in time without another action.我的意思是如果不采取其他行动,它不会在某个时间点返回。 I feel there is no need for async in this class But if it necessary, I've drafted code that might help我觉得在这个 class 中不需要async但是如果有必要,我已经起草了可能有帮助的代码

    class ModelObject {
        private CancellationTokenSource cts;
        private CancellationToken ct;
        private Progress<int> progress;
        private Task doSomethingTask;
        private async Task DoSomething(IProgress<int> progress){
          ...
           while (DoingSomething)
           {
               if (ct.IsCancellationRequested)
               {
                   // Cancel work and return
               } 
            ...
           }       
        }

        public async void StartAsync () {
            progress = new Progress<int>(...);
            cts = new CancellationTokenSource ();
            ct = cts.Token;
            doSomethingTask = DoSomething (progress);
            await doSomethingTask;
        }

        public void Stop () {
            cts.Cancel();
            doSomethingTask.Wait();
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM