【问题标题】:How to get intermediate results from long running operation?如何从长时间运行的操作中获得中间结果?
【发布时间】:2015-01-23 05:14:09
【问题描述】:

参加以下课程,假设Calculate 是一个计算密集型函数。

class Algorithm
{
    FinalResultObject Calculate()
    {
        longPartialCalculation();
        //signal to caller that that part is ready of type MidResult1
        morePartialCalculation();
        //signal more is ready, different type of  MidResult2
        moreWork();
        return finalResult;             
    }
}

现在假设,只要用户准备好,中间结果就需要显示给用户。

我看到的选项是:

  1. 使用单独的事件发出信号

  2. 使用构造函数注入来注入正在调用其方法的处理程序类

  3. 使用 RX observables

我是 RX 新手,但我喜欢我可以轻松地在 UI 线程上进行事件处理的想法。我想知道这是否是矫枉过正而不是预期的,因为它不是真正的整个数据流,而只是每个可观察的一个结果。另一方面,虽然就像事件订阅和取消订阅一样,看起来很麻烦。

有什么提示吗?

【问题讨论】:

  • 除非这个工人的寿命比它报告的进度要长得多(几乎永远不会如此),否则没有必要取消订阅事件;持有事件的对象应该在任何添加处理程序的对象之前死亡。
  • 我不确定我是否理解这个问题,但是除了提供特殊的进度报告事件之外,您还可以轮询正在进行的操作,那么您不需要事件、订阅和取消订阅。
  • 我对这个问题的理解不是如何从一些封闭的操作中获得进展,而是如何报告计算提供的进展。这只是一个如何公开进度通知的问题。
  • @Servy 所以你建议走的路是通过事件? UI 肯定会比Algorithm 类更长寿。但由于 Calculate() 不会在 UI 线程上运行,我需要将事件处理程序引导回调度程序。
  • @所有人:我实际上在谷歌上搜索这个问题时遇到了麻烦。似乎我无法真正找到我想知道的问题的真实世界样本,因为它感觉很普遍。我需要封装Algorithm 类,因为它需要可互换。中间步骤没有特定的顺序,这就是为什么我不能公开几个方法,比如CalculationStep1() 等等。

标签: c# events dependency-injection system.reactive


【解决方案1】:

解决这个问题的 Rx 方法是定义一个 cold observable,如下所示:

IObservable<Result> Calculate(IScheduler scheduler)
{
  return Observable.Create<Result>(observer => 
    scheduler.Schedule(() =>
    {
       observer.OnNext(longPartialCalculation());
       observer.OnNext(morePartialCalculation());
       observer.OnNext(moreWork());
       observer.OnCompleted();
    }));
}

// Depending upon your needs, you could use inheritance as follows:
public abstract class Result { ... }
public class MidResult1 : Result { ... }
public class MidResult2 : Result { ... }
public class FinalResultObject : Result { ... }

您还可以定义一个指定默认调度程序的重载,例如 ThreadPoolScheduler 如果您想引入并发,或者 CurrentThreadScheduler 如果您不想。

要使用此方法返回的可观察对象,只需使用观察者调用Subscribe。您可以提供一个OnNext 处理程序来检查每个到达时的Result 对象,并提供一个OnCompleted 处理程序来处理完成。如果必须,您还可以提供OnError 处理程序来处理Exception(编辑:请注意,OnError 并没有被我的示例调用。)

如果您想确保所有这些处理程序都在 UI 线程上执行,并且您已将引入并发的调度程序(例如 ThreadPoolScheduler)传递给 Calculate 方法,那么您还可以应用 @987654334 @ 运算符(或基于 XAML 的平台上的 ObserveOnDispatcher)将所有通知编组到 UI 线程以供观察。

algo.Calculate(ThreadPoolScheduler.Instance)
    .ObserveOnDispatcher()
    .Subscribe(OnNextResult, OnCompleted);

请注意,Rx 的主要好处之一是能够查询;例如,一个简单的过滤器:

algo.Calculate(ThreadPoolScheduler.Instance)
    .Where(result => result.HasRequiredState)
    .ObserveOnDispatcher()
    .Subscribe(result => handle(result.RequiredState));

【讨论】:

  • 我想过只有一个 observable 的想法。我无法证明抽象 Result 类(甚至是接口)的合理性,因为它可能共享的所有内容都是 public object Value{get;},这意味着在 OnNextResult() 中进行类型检查和强制转换。另一方面,它可能是空的,在继承类上留下显式类型的属性。然后Result 类的唯一目的是将所有结果合并到一个 Observable 中。这就是为什么我想在 Algorithm 类上公开几个 Observables(每个结果一个)。
  • 拥有多个 observable 的问题是同步。 Observables 代表 Rx 中的并发性,因此具有多个意味着例如 2 个观察者可以同时接收通知。当然,无论如何,您都在编组到 UI 线程,尽管调度程序的可观察对象之间仍然可能存在竞争,这取决于您将如何编码。无论如何,在 Rx 中,在单个 observable 中建模一系列通知更为自然——这就是它的用途!
  • Result 类只是携带附加信息的一种方式。也许你根本不需要它。只需使用IObservable&lt;T&gt;,其中T 是当前计算值;例如,IObservable&lt;int&gt; 可以表示完成百分比,例如立即推送 0,中途推送 50,在调用 OnCompleted 之前立即推送 100。
  • 继承的想法只是一个建议。如果您需要成对的数据,您还有其他选择;例如,您可以在枚举中对类型进行编码并将其作为属性提供给 Result 类,或者您可以只使用 Tuple&lt;Phase, Value&gt; 代替。 Result 类只是比Tuple 更明确的一种方式,因为使用语义属性总是比使用Item1Item2 等要好得多。
  • 您可能会发现我的一篇博文对您有帮助:davesexton.com/blog/post/Rx-The-Power-of-T.aspx
【解决方案2】:

您可以使用 .Net 的 Progress&lt;T&gt;。您创建一个实例,传递一个处理程序或注册到它的事件,并在整个长时间运行的过程中通过它进行报告:

var progress = new Progress<string>(value => Console.WriteLine(value));
Calculate(progress);

FinalResultObject Calculate(IProgress<string> progress)
{
    longPartialCalculation();
    progress.Report("MidResult1");
    morePartialCalculation();
    progress.Report("MidResult2");
    moreWork();
    return finalResult;
}

在这种情况下,报告正在向控制台写入一个字符串,但您当然可以将其用于任何您想要的类型。

Progress&lt;T&gt; 还会在创建时捕获当前的SynchronizationContext,因此您可以在 UI 线程中创建它,并将其传递给非 UI 线程,而不会出现任何同步问题。

【讨论】:

  • 有趣!这是我以前没有遇到过的一门课!它看起来与 RX 的 Observables 非常相似,并且出现了相同的问题:由于中间结果是不同类型的,我需要类型检查和强制转换或 Progress&lt;T&gt;'s 。不过感谢您的选择!
  • @MarkusHütter 这取决于您打算通知什么。最简单的选项是百分比,所以Progress&lt;int&gt;。我会做的可能是创建一个包含所有阶段的CalculateProgress 枚举,并在每个阶段完成时报告。
  • ... 然后将每个阶段的结果拉到枚举上?
  • @MarkusHütter 你可以。同样,这取决于您使用结果的目的。您甚至可以使用Progress&lt;Action&gt; 发布实际的代表来执行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-17
  • 2019-06-16
  • 2011-01-10
相关资源
最近更新 更多