c# - 避免冗余的异步计算

标签 c# .net task async-await

我有一些 UI 代码,其中我有一个如下所示的方法:

    private async Task UpdateStatusAsync()
    {
        //Do stuff on UI thread...

        var result = await Task.Run(DoBunchOfStuffInBackground);

        //Update UI based on result of background processing...
    }

目标是让 UI 在任何影响其状态的属性更改时更新相对复杂的计算状态。这里有几个问题:

  1. 如果我直接从每个更新状态的地方调用这个方法,最终更新的状态可能不正确。假设属性 A 发生变化,然后属性 B 发生变化。即使 B 在 A 之后调用 UpdateStatusAsync,有时回调代码(最终 UI 更新)也会以相反的顺序发生。所以:(A -> 更新) -> (B -> 更新) -> (B 更新) -> (A 更新)。这意味着最终 UI 显示陈旧状态(反射(reflect) A,但不反射(reflect) B)。
  2. 如果我总是等待先前的 UpdateStatusAsync 先完成(我目前正在做的事情),我可能会多次执行昂贵的状态计算。理想情况下,我只需要为一系列更新执行“最后”计算。

我正在寻找的是一个可以完成以下任务的简洁模式:

  1. 最终状态永远不会“陈旧”超过一小段时间(即我不希望 UI 与基础状态不同步)
  2. 如果在短时间内发生多次更新调用(常见用例),我宁愿避免重复工作,而是始终计算“最新”更新。
  3. 由于在某些情况下可能会非常接近地(即在几毫秒内)发生多个更新,因此如果有一种方法可以避免在短时间内启动处理以防其他更新请求进入,这将很方便。

这似乎应该是一个相当普遍的问题,所以我想我会在这里问问是否有人知道这样做的特别干净的方法。

最佳答案

嗯,最直接的方法是使用 CancellationToken 取消旧状态更新,使用 Task.Delay 延迟状态更新:

private CancellationTokenSource cancelCurrentUpdate;
private Task currentUpdate;
private async Task UpdateStatusAsync()
{
  //Do stuff on UI thread...

  // Cancel any running update
  if (cancelCurrentUpdate != null)
  {
    cancelCurrentUpdate.Cancel();
    try { await currentUpdate; } catch (OperationCanceledException) { }
    // or "await Task.WhenAny(currentUpdate);" to avoid the try/catch but have less clear code
  }

  try
  {
    cancelCurrentUpdate = new CancellationTokenSource();
    var token = cancelCurrentUpdate.Token;
    currentUpdate = Task.Run(async () =>
    {
      await Task.Delay(TimeSpan.FromMilliseconds(100), token);
      DoBunchOfStuffInBackground(token);
    }, token);

    var result = await currentUpdate;

    //Update UI based on result of background processing...
  }
  catch (OperationCanceledException) { }
}

不过,如果您真的更新得很快,这种方法会为 GC 产生(甚至)更多垃圾,而且这种简单的方法总是会取消旧状态更新,因此如果事件中没有“中断”,UI 可能最终会落后很多。

这种复杂程度是 async 开始达到其极限的地方。 Reactive extensions如果您需要更复杂的东西(例如处理那个“中断”以便您至少每隔一段时间更新一次 UI),那将是更好的选择。 Rx 特别擅长处理时序。

关于c# - 避免冗余的异步计算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15396911/

相关文章:

c# - 如何可靠地知道什么对象是当前(聚焦)outlook 窗口。即 explorer 类型或 Inspector 类型

c# - 在 Linq 的循环中添加 OR 表达式

c# - 监视自定义 Windows 服务

java - 我可以让自动化 java 应用程序忽略 JOptionPanes 吗?

c# - 使用C#API的 Elasticsearch 批量索引

c# - 如何将8bit声音转换为16bit

c# - BinaryFormatter.ILMerge 后反序列化 "unable to find assembly"

python - 树莓派 : How can I kill an auto-started Tkinter UI?

testing - 如何在 doFirst/doLast 闭包中执行内置的 gradle 任务?

C# 类属性可枚举