C# 如何从长期运行的操作中获得中间结果?

C# 如何从长期运行的操作中获得中间结果?,c#,events,dependency-injection,system.reactive,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

假设
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;             
    }
}
现在假设,中间结果需要随时向用户显示

我看到的选择是:

  • 使用单独的事件发出信号

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

  • 使用RX观测值

  • 我是RX新手,但我喜欢这样的想法,即我可以轻松地在UI线程上进行事件处理。我想知道这是不是太过了,也不像预期的那样,因为它不是一个完整的数据流,而是每个可观察到的数据只有一个结果。另一方面,尽管与活动一样,订阅和取消订阅似乎非常麻烦


    有什么提示吗?

    解决这个问题的方法是定义一个冷的可观察的,如下所示:

    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 { ... }
    
    请注意,Rx的主要优点之一是能够查询;e、 例如,一个简单的过滤器:

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

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

    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;
    }
    
    var progress=newprogress(value=>Console.WriteLine(value));
    计算(进度);
    FinalResultObject计算(IProgress进程)
    {
    长部分计算();
    进度报告(“中期结果1”);
    更多部分计算();
    进度报告(“中期结果2”);
    moreWork();
    返回最终结果;
    }
    
    在本例中,报告正在向控制台写入字符串,但您当然可以将其用于所需的任何类型


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

    除非此工作人员将比其报告进度的任何工作人员活得更长(这几乎永远不会发生)没有必要取消订阅活动;持有事件的对象应该在任何对象向其添加处理程序之前死亡。我不确定是否理解此问题,但除了提供特殊的进度报告事件外,您还可以轮询正在进行的操作,这样您就不需要事件,订阅和取消订阅。我对这个问题的理解不是如何从一些封闭的操作中获得进度,而是如何报告计算所产生的进度。这只是一个如何公开进度通知的问题。@Servy那么你是说应该通过事件来实现吗?UI肯定会比
    算法
    类更有效。但是由于
    Calculate()
    不会在UI线程上运行,所以我需要将事件处理程序传回调度程序。@各位:实际上我在谷歌搜索这个问题时遇到了麻烦。似乎我真的找不到我的问题的真实世界样本,我想知道,因为它感觉很普遍。我需要封装
    算法
    类,因为它需要可互换。中间步骤没有特定的顺序,这就是为什么我不能公开一些方法,比如
    CalculationStep1()
    等等。我考虑过只使用一个可观察的方法。我在解释抽象的
    结果
    类(甚至是接口)时遇到了困难,因为它可能共享的只是
    公共对象值{get;}
    ,这意味着在
    OnNextResult()中进行类型检查和强制转换。另一方面,它可能为空,在继承类上留下显式类型化的属性。然后,
    结果
    类的唯一目的是将所有结果合并到一个可观察的结果中。这就是为什么我想在
    算法
    类上公开几个可观察对象(每个结果一个)。拥有多个可观察对象的问题是同步。Observable表示Rx中的并发性,因此有多个Observable意味着,例如,两个Observable可以同时接收通知。当然,无论如何,您都在封送到UI线程,尽管在分派器的可观察对象之间仍然可能存在竞争,这取决于您将如何对其进行编码。无论如何,在Rx中,在一个可观察对象中建模一系列通知更为自然——这就是它的用途!
    Result
    类只是一种承载附加信息的方式。也许你根本不需要它。只需使用
    IObservable
    ,其中
    T
    是当前计算值;e、 例如,
    IObservable
    可以表示完成百分比,例如在调用
    OnCompleted
    之前立即推送0,中途推送50,然后立即推送100。继承的想法只是一个建议。如果您需要成对的数据,您还有其他选择;e、 例如,您可以在枚举中对该类进行编码,并将其作为
    结果
    类的属性提供,或者您可以使用
    元组
    Result
    类只是比
    Tuple
    更显式的一种方式,因为使用语义属性总是比使用
    Item1
    Item2
    等更好。你可能会发现我的一篇博文很有帮助:有趣!这是我以前从未遇到过的一门课!它看起来非常类似于RX的观测结果,但同样的问题也出现了:由于中间结果的类型不同,我需要进行类型检查和类型转换或
    进度
    。谢谢你的选择@这取决于你打算通知什么。最简单的选项是百分比,因此
    进度
    。我可能会做的是创建一个包含所有阶段的
    CalculateProgress
    enum,并在每个阶段完成时报告。。。。然后在enum?@MarkusHütter上拉取每个阶段的结果。同样,这取决于您使用的是什么
    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;
    }