如何从长时间运行中获得中间结果?

时间:2015-01-21 15:10:51

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

采用以下类,假设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

  4. 我是RX的新手,但我喜欢我可以轻松地在UI线程上进行事件处理的想法。我想知道这是否过度而且不是预期的,因为它不是真正的整个数据流,而是每个可观察数据的一个结果。另一方面,虽然与事件订阅和取消订阅似乎是如此繁琐。

    任何提示?

2 个答案:

答案 0 :(得分:3)

解决此问题的Rx方法是定义可观察对象,如下所示:

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如果不引入。

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

如果要确保所有这些处理程序都在UI线程上执行,并且您已将引入并发的调度程序(例如ThreadPoolScheduler)传递给Calculate方法,那么您还可以在基于XAML的平台上应用ObserveOn运算符(或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));

答案 1 :(得分:2)

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

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<T>还会捕获创建时的当前SynchronizationContext,以便您可以在UI线程中创建它,将其传递给非UI线程而不会出现任何同步问题。