报告从存储库到UI的进度的好方法是什么

时间:2011-12-31 15:12:56

标签: c# design-patterns repository system.reactive

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

我的情况如下:Repository中的Infrastructure LayerData Layer进行通信。虽然这两个正在运行,但UI目前还没有关于正在发生的事情的线索,只是被动地等待结果(使用Observable)

我的第一个想法是公开UI绑定到的存储库中的事件。但我觉得这可能会变得非常混乱。我很想知道你们是如何解决这个问题的。

4 个答案:

答案 0 :(得分:4)

我在后台处理和报告进度方面做了很多工作,所以我完全理解它可以获得多么“混乱”。

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

我建议你看看项目中的一些单元测试,因为那里有很多用法示例。还有一个可运行的演示来查看代码的实际运行情况。

一般概念

在创建图书馆的过程中,我遇到了一些“混乱”的问题。以下是我解决这些问题的方法:

  1. 从多个层报告进度
    有一段时间,我正在将一个共同的“进度”对象传递到每个图层,以便每个图层都可以向其报告。这种方法很糟糕。每个方法都必须传递对象,即使它没有使用它 相反,我决定使用一个像单身人士一样的静态类Progress。这样,每一层都直接报告进度(如果需要) 可以通过附加到事件或通过轮询来监视进度 这种方法使得Progression非常容易报告和阅读进度。

  2. 线程安全
    当你有一个显示后台任务进度的UI时,通常只需要计算进度,对吧?显然,我必须真正考虑线程安全 但是,我真的想避免使用典型的锁定机制,因为它们很昂贵,我绝对不想放慢已经很慢的后台任务。所以,我的解决方案是使用后台任务创建一个独立的ProgressChangedInfo对象,可以在线程之间安全地传递。

  3. 交叉线程
    最初,我的后台进程报告了数千个进度事件,并为每个进程事件调用了UI更新。用户界面会停止,试图赶上所有更新 所以我介绍了“轮询”,但我发现“重要的”进展,例如“复制文件2的3”,正在被放弃,转而支持不太重要的更新,例如“文件5%复制”。
    现在,“重要”更新将始终优先于不太重要的更新,因此UI可以尝试尽快更新,始终显示信息最丰富的消息,并且UI将始终保持完全响应。

  4. 进展特定信息

    • 任务表示具有多个步骤的过程。完成步骤后,任务将报告进度。
    • 各个方法负责启动任务,完成步骤和结束任务
    • 嵌套任务报告进度时,会针对整体进度进行适当计算。嵌套任务的数量没有限制。
    • 监控进度有两种主要方式:
      • 您可以附加到活动
      • 您可以“轮询”进度

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

    • 有几种聪明的方法来计算进度:
      • 均匀加权的步数;例如,一个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();
    }
    

答案 1 :(得分:2)

Uese要么是活动,要么是(裸)代表。

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

// Repository
public delegate void ReportProgress(int percentage);

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

经典的方法是让你的非用户可见图层显示“[Unr | R] egisterProgressCallback()”方法和暴露拟合回调的UI层,现在你的胶水代码可以将它们放在一起。