【问题标题】:How to cancel async work gracefully?如何优雅地取消异步工作?
【发布时间】:2014-03-13 20:05:15
【问题描述】:

我有一个 UI 应用程序,它每秒记录一些内容。每次OpenFileHandler 被触发时,我都必须开始记录到一个新文件。这是代码的一个非常简化的版本:

string _logItem;
string _fileName;
CancellationTokenSource _cts;
Task _task;

private void OpenFileHandler(object sender, EventArgs e)
{
    // once OpenFileHandler has been fired, 
    // the _logItem should go to the new file

    if (_task != null && !_task.IsCompleted)
    {
        _cts.Cancel();
        // can't do: _task.Wait();
    }

    if ( _fileName != null )
    {
       _cts = new CancellationTokenSource();
       _task = LogAsync(_fileName, _cts.Token);
    }
}

private async Task LogAsync(string fileName, CancellationToken ct)
{
    using (var writer = new System.IO.StreamWriter(fileName, false))
    {
        try
        {
            while (true)
            {
                await Task.Delay(1000, ct);
                await writer.WriteLineAsync(_logItem);
            }
        }
        finally
        {
            writer.WriteLine("end of log!");
        }
    }
}

问题:OpenFileHandler 是同步的,但我需要确保待处理的WriteLineAsync 已完成并且旧日志文件已关闭,然后才能开始新的LogAsync 任务。

我不能在OpenFileHandler 内执行_task.Wait(),因为它会阻塞 UI 线程。

我也无法将OpenFileHandler 设为async 方法并在其中执行await _task。这是因为当应用程序关闭时,OpenFileHandler 被触发,_fileNamenull,我希望 "end of log!" 行仍然存在于日志中。

我该如何解决这个问题?

【问题讨论】:

标签: c# .net events asynchronous async-await


【解决方案1】:

作为LogAsync的第一行,等待旧任务:

await prevTask;

您不知何故需要传递上一个任务。也许作为方法参数。

您可能需要获取OperationCancelledException,或者:

await prevTask.ContinueWith(_ => { }); //suppress exceptions

【讨论】:

  • 可能是这样,但是如何确保在应用退出时我仍然得到end of log!?可能不再有 UI 消息循环迭代。
  • 替代方案:a) 保持消息循环运行,直到最后一个日志记录任务完成。消息循环不需要 UI。 b) 使用 ConfigureAwait(false) 完全不依赖 UI。您可能希望将 Wait 作为日志记录任务的最后一行 Main
  • AsyncLog 中的延续使用WinFormsSynchronizationContext.Post 进行排队,因此我必须在应用程序退出时泵送消息以执行它。 ConfigureAwait(false) 可能确实有帮助,那么我可能会做Wait()。在我的应用程序中,LogAsync 也更新了 UI,如果我使用 ConfigureAwait(false),我必须使用 BeginInvoke
  • 我想说从该方法更新 UI 在架构上无论如何都是不可取的。考虑使用IProgress 通知 UI,或者公开一个事件供 UI 使用。
【解决方案2】:

您不能处理关闭应用程序的特殊情况(并将 null 传递给OpenFileHandler 方法)吗?

因此,在应用程序关闭的情况下,您可以先_cts.Cancel(),然后再_task.Wait(),以允许完成写入日志。

您不能检查CancellationToken 是否已被取消,而不是执行while(true),以及是否已退出并写入“日志结束!”

【讨论】:

  • 那么我会放弃end of log!。如果你在关机的时候做_task.Wait(),还是会导致死锁
  • 检查 CancellationToken - 我认为这不会有帮助,如果我在请求取消后不await 这个任务,这个检查可能没有机会执行。
  • 是的,我建议你让 OpenFileHandler 异步,然后取消,然后在关闭应用程序时等待任务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-29
  • 1970-01-01
  • 2018-07-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多