首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >避免冗余异步计算

避免冗余异步计算
EN

Stack Overflow用户
提问于 2013-03-13 21:33:33
回答 4查看 292关注 0票数 3

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

代码语言:javascript
复制
    private async Task UpdateStatusAsync()
    {
        //Do stuff on UI thread...

        var result = await Task.Run(DoBunchOfStuffInBackground);

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

用户界面的目标是随时更新影响其状态的属性更改时相对复杂的计算状态。这里有几个问题:

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

我正在寻找的是一个干净的模式,它完成了以下工作:

  1. 在短时间内,最终状态不应该“过时”(也就是说,我不希望UI与底层状态不同步)。
  2. 如果多个更新调用发生在短时间内(一个常见的用例),我宁愿避免重复的工作,而总是计算“最新”更新。
  3. 由于几种情况下,多个更新可能发生在非常接近(即毫秒内),因此有一种方法可以避免在短时间内开始处理,以防其他更新请求出现。

这似乎是一个相当常见的问题,所以我想我想问一下,如果有人知道一个特别干净的方法来做这件事。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-03-13 22:38:47

你不需要使用计时器就能做到这一点。一般而言:

代码语言:javascript
复制
private async Task UpdateStatusAsync()
{
    //Do stuff on UI thread...

    set update pending flag

    if currently doing background processing
    {
        return
    }

    while update pending
    {
        clear update pending flag
        set background processing flag
        result = await Task.Run(DoBunchOfStuffInBackground);
        //Update UI based on result of background processing...
    }
    clear background processing flag
}

我将不得不思考如何在异步/等待的上下文中准确地完成所有这些工作。我在过去对BackgroundWorker做过类似的事情,所以我知道这是可能的。

防止它丢失更新应该是非常容易的,但是它可能会时不时地做一个不必要的后台处理。但是,如果在很短的时间内发布10次更新,当然可以避免做9次不必要的更新(可能,它只会做第一次和最后一次更新)。

如果需要,可以将UI更新移出循环。取决于您是否介意看到中间更新。

票数 1
EN

Stack Overflow用户

发布于 2013-03-13 22:38:24

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

代码语言:javascript
复制
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开始达到极限的地方。如果您需要更复杂的内容,反应性扩展将是一个更好的选择(比如处理“中断”,这样您至少会经常得到一个UI更新)。Rx特别擅长处理时间问题。

票数 2
EN

Stack Overflow用户

发布于 2013-03-13 22:22:38

既然我似乎走在正确的轨道上,我将提出我的建议。在非常基本的伪代码中,这看起来可能会奏效:

代码语言:javascript
复制
int counter = 0;

if (update received && counter < MAX_ITERATIONS)
{
     store info;
     reset N_MILLISECOND timer;
}
if (timer expires)
{
    counter = 0;
    do calculation;
}

这将让您跳过太多的电话是太接近对方,而计数器将确保您仍然保持最新的用户界面。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15396911

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档