从Repository向UI报告进度的好方法是什么?

本文关键字:方法 是什么 Repository UI 报告 | 更新日期: 2023-09-27 18:19:05

我正在寻找一种好方法,或者至少是一些见解,关于如何干净地向UI层报告进度。

我的情况是这样的:我在Infrastructure Layer中得到一个Repository,它与Data Layer通信。当这两个正在工作的UI目前没有线索正在发生什么,只是被动地等待结果(使用一个可观察的)

我的第一个想法是公开UI绑定到的Repository中的事件。但我觉得这可能会很乱。我很想听听你们是怎么解决的。

从Repository向UI报告进度的好方法是什么?

我做过很多后台处理和报告进度的工作,所以我完全理解这是多么的"混乱"。它可以得到。

我创建了一个开源库,它确实为报告进度提供了一个很好的框架,特别是在"嵌套"中。场景!查看进度。

我建议看一下项目中的一些单元测试,因为那里有很多使用示例。还有一个可运行的演示程序,可以查看代码的运行情况。

的一般概念

在创建库的过程中,我遇到了几个"混乱"的"问题。我是这样解决的:

  1. 从多层报告进度
    有一次,我正在通过一个普通的"进度"考试。对象放到每一层,这样每一层都可以向它报告。这种方法很糟糕。每个方法都必须传递对象,即使它不使用它。
    相反,我决定使用一个静态类Progress,它的行为就像一个单例。这样,每一层都可以直接报告进度(如果需要的话)。
    可以通过附加到事件或轮询来监视进度。
    这种方法使得使用Progression来报告和读取进度非常容易。

  2. 线程安全
    计算进度通常只在UI显示后台任务进度时才需要,对吧?显然,我必须认真考虑线程安全性。
    然而,我确实希望避免使用典型的锁定机制,因为它们代价高昂,而且我绝对不想减慢已经很慢的后台任务的速度。所以,我的解决方案是使用后台任务来创建一个独立的ProgressChangedInfo对象,它可以安全地在线程之间传递。

  3. 交叉线程
    最初,我的后台进程报告数千个进程事件,并为每个事件调用UI更新。UI会停滞不前,试图赶上所有的更新。
    所以我引入了"民意调查",但我发现这"很重要"诸如"复制文件2/3"之类的进度被放弃,取而代之的是一些不太重要的更新,如"复制文件5%"。
    现在,"important"更新总是比不太重要的更新更受青睐,因此UI可以尝试尽可能快地更新,总是显示最具信息量的消息,并且UI将始终保持完全响应。

进展具体信息

  • 一个Task表示一个过程有几个步骤。当一个步骤完成后,Task会报告进度。
  • 单个方法负责启动任务、完成步骤和结束任务
  • 嵌套的任务报告进度时,它会根据总体进度进行计算。嵌套任务的数量没有限制。
  • 有两种主要的监测进展的方法:
    • 您可以附加到一个事件
    • 你可以"投票"

以下是一些特别酷的功能:

    有几个聪明的方法来计算进度:
    • 平均加权步数;例如,for循环有20次迭代,每次值5%
    • 加权步数;例如,3个步骤占10%、10%、80%
    • 支持未知的步数!例如,从数据库中读取行。在这个场景中,您提供了一个"估计",当读取每一行时,进度将向前推进,但在完成之前永远不会达到100%。
  • 进度计算是完全LAZY的-所以如果你在低级别实现进度计算,但没有"监听器",那么进度将被完全跳过
  • 几个线程安全的特性是内置的,所以轮询和事件处理可以由前台线程完成
  • 几个扩展方法是可用的,使代码非常容易使用…例如,IEnumerable<T>.WithProgress()将在迭代时报告所有进度!

下面是一些代码:

public void PerformSomeTask() {
    // This task has 2 steps; the first step takes about 20%, and the second takes 80%:
    Progress.BeginWeightedTask(20, 80);
    
    // Start the first step (20%):
    Progress.NextStep();
    TaskA();
    
    // Start the second step (80%):
    Progress.NextStep();
    TaskB();
    
    // Finished!
    Progress.EndTask();     
}

public void TaskA() {
    int count = 20;
    // This task has 20 steps:
    Progress.BeginFixedTask(count);
    
    for (int i = 0; i < count; i++) {
        Progress.NextStep();
        // ...
    }
    
    Progress.EndTask();
}
public void TaskB() {
    // This task has an unknown number of steps
    // We will estimate about 20, with a confidence of .5
    Progress.BeginUnknownTask(20, .5);
    
    while (database.ReadRow()) {
        Progress.NextStep();
        // ...
    }
    Progress.EndTask();
}

使用事件或(裸)委托。

由于您可能有多个调用,委托参数可能是最好的(不必从事件取消注册)。

// Repository
public delegate void ReportProgress(int percentage);
  public IEnumerable<X> GetSomeRecords(..., ReportProgress reporter)
  {
      ...;
      if (reporter != null)
          reporter(i * 100 / totalRecords);  
  }

如何返回从0-100报告进度的IObservable<int> ?这不仅具有返回进度的优点,而且还允许您在出现错误时返回错误(通过OnError)。

经典的方法是让你的非用户可见层暴露"[Unr|R]egisterProgressCallback()"方法和UI层暴露合适的回调,现在你的胶水代码可以把它们放在一起,因为它喜欢