如何捕获 Task.Delay() 取消并进行回调?

How to catch that Task.Delay() canceled and do a callback?

假设我有一个工人 class。

public sealed class Worker : IDisposable
{
    private bool _isRunning;
    private CancellationTokenSource _cts;

    private readonly Action _action;
    private readonly int _millisecondsDelay;
    private readonly object _lock = new object();

    public Worker(Action action, int millisecondsDelay)
    {
        _action = action;
        _millisecondsDelay = millisecondsDelay = 5000;
    }

    public void Start()
    {
        lock (_lock)
        {
            if (!_isRunning)
            {
                _isRunning = true;
                Run();
            }
        }
    }

    public void Cancel()
    {
        lock (_lock)
        {
            if (_isRunning) _cts.Cancel();
        }
    }

    private void Run()
    {
        using (_cts) _cts = new CancellationTokenSource();
        Task.Run(async () => { await DoAsync(_cts.Token); });
    }

    private async Task DoAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            //Log.Message1("____REFRESHING STATUS____");
            _action();
            await Task.Delay(_millisecondsDelay, cancellationToken);
        }

        //this code is unreachable
        lock (_lock)
        {
            _isRunning = false;
        }
    }

    public void Dispose()
    {
        try
        {
            _cts?.Cancel();
        }
        finally
        {
            if (_cts != null)
            {
                _cts.Dispose();
                _cts = null;
            }
        }
    }
}

问题是代码 _isRunning = false; 无法访问。我的意思是更有可能当调用者调用 Cancel 方法时工作人员将等待 Task.Delay。那么在我的任务被取消后我如何调用 smth(这里是 _isRunning = false;)?换句话说,我需要确保我的工人不是 运行(它不是取消状态)

要回答您的字面问题,您可以使用 finally 块:

private async Task DoAsync(CancellationToken cancellationToken)
{
  try
  {
    while (!cancellationToken.IsCancellationRequested)
    {
      //Log.Message1("____REFRESHING STATUS____");
      _action();
      await Task.Delay(_millisecondsDelay, cancellationToken);
    }
  }
  finally
  {
    lock (_lock)
    {
      _isRunning = false;
    }
  }
}

但我对这种 "worker" 方法有些担忧:

  • 我不太喜欢里面的即发即弃式 Run。我怀疑你会想要改变它。
  • lock 与异步代码混合使用可能会出现问题。你应该绝对确定这是你真正想做的。

可能值得退后一步,重新考虑您实际想要使用此代码做什么。