【问题标题】:Cancel and wait on async Task methods取消并等待异步任务方法
【发布时间】:2020-06-29 02:06:32
【问题描述】:

试图弄清楚这一点,但找不到任何确定的东西。

假设我有一个类可以异步执行一些工作并且可以动态启动或停止:

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

DoSomething现有实例 取消时,我不确定如何await

典型的用法是这样的:

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

任何帮助将不胜感激。

【问题讨论】:

  • 请注意:async void 不好。应该是async Task
  • minimal reproducible example 在这里会很棒。很难说出您正在清理什么,因此很难提供解决方案。
  • @Enigmativity 嗨做了一个小改动,希望这可能会有所帮助。清理,如删除正在处理的文件等。
  • 您阅读minimal reproducible example 页面了吗?你肯定没有提供任何类似的东西。

标签: c# asynchronous async-await task


【解决方案1】:

我的偏好是将Completion 属性添加到ModelObject 类。调用者可以在取消操作后选择await 或不Completion。此外,我可能会防范不当使用 API。在现有操作挂起时尝试​​启动新操作会导致异常。

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

另一条建议:如果取消,请确保您的 DoSomethingAsync 抛出 OperationCanceledException。这是可取消异步操作的expected behavior。例如,如果您将令牌传递给任何内置 API,您会注意到返回的任务将在调用 _cts.Cancel() 方法后立即以 Canceled 完成。要模仿这种行为,您可以在循环内调用_ct.ThrowIfCancellationRequested(),并将令牌传递给任何接受它的API(例如Task.Delay 方法)。事实上,在内部捕获这个异常可能会更好,并在 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
    }
}

【讨论】:

  • 感谢您的详细解释。我在 Stop() 方法中执行清理的原因是因为我希望能够改变清理,例如Stop()Cancel()Pause() 或其他东西的不同清理。我的目标是让DoSomething Task 只关注实际任务而不处理清理工作。此外,Start() 方法应该只关注启动Task
  • @antikbd 在我的示例中,Startasync void,所以从调用者的角度来看,它只不过是启动任务而已。它还为任务附加了一个延续,以便在任务完成时更新类的内部状态,这对调用者是透明的。关于对不同风格的Stop 进行不同的清理,您可以为每个Stop 设置不同的CTS,或者设置一些额外的状态,例如:public void Stop1() { _stopType = 1; _cts?.Cancel(); }
  • 谢谢。是的,这是我目前的方法 - 使用多个 CancellationTokens。但是,我希望我不必检查多个Tokens(由于存在错误的可能性)并且可以只使用一个Token 和多种方法。
  • @antikbd 有不同的方法都试图改变内部状态,希望调用者不会在错误的时间或多次或不正确的顺序调用它们,对我来说似乎更容易出错!
【解决方案2】:

虽然您可以等待从DoSomething 返回的任务,但您也可以使用TaskCompletionSource

 私有 TaskCompletionSource tcs;

        私有异步任务 DoSomething(IPProgress 进度){
          ...
           同时(做某事)
           {
               如果(ct.IsCancellationRequested)
               {
                   // 取消工作并返回
               }
            ...
           }
           tcs.SetResult(null);
        }

        公共异步任务停止(){
           cts.Cancel();
           等待 tcs.Task;
        }

【讨论】:

    【解决方案3】:

    我不确定为什么StartAsync 方法是异步的,因为除非调用Stop,否则它会无限期地执行。我的意思是如果不采取其他行动,它不会在某个时间点返回。我觉得这个班不需要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();
            }
        }
    

    【讨论】:

    • 如果答案不建议使用.Wait 这实际上是完整的解决方案...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 2018-02-23
    • 1970-01-01
    • 2023-03-22
    相关资源
    最近更新 更多