【问题标题】:Using RX to synchronize multiple events使用 RX 同步多个事件
【发布时间】:2014-11-04 13:45:53
【问题描述】:

假设我在一个列表中有三个产品。为了启用某个动作,所有三个都需要属于某种类型。为了了解产品的类型,我需要拨打服务电话并等待响应。

我想做的是等待所有三个响应(如果出现问题,可能会超时),当收集到所有信息后,决定是否启用可能的操作。

我曾经通过一些计数器或重置事件来跟踪已完成的事件来解决这个问题,但我想看看我是否可以使用 Rx 以更简洁的方式来完成它。

由于我对 Rx 还不太熟悉,所以我正在寻找一些提示/指针。我知道我可以使用

Observable.FromEventPattern

对于我正在等待的事件。我订阅并等待响应并处理它。我只是不清楚如何组合多个事件。

【问题讨论】:

    标签: c# system.reactive


    【解决方案1】:

    你要找的组合子是CombineLatest

    假设你有这样的课程:

    public class Foo
    {
        public delegate void FooEventHandler(object sender, EventArgs args);
    
        public event FooEventHandler FirstEvent = delegate {};    
        public event FooEventHandler SecondEvent = delegate {};    
        public event FooEventHandler ThirdEvent = delegate {};    
    
        public void DoIt()
        {
            FireOne();
            FireTwo();
            FireThree();
        }
    
        public void FireOne()
        {
            Console.WriteLine("Firing event 1...");
            Thread.Sleep(1000);
            FirstEvent(this, new EventArgs());
        }
        public void FireTwo()
        {
            Console.WriteLine("Firing event 2...");
            Thread.Sleep(1000);
            SecondEvent(this, new EventArgs());
        }
        public void FireThree()
        {
            Console.WriteLine("Firing event 3...");
            Thread.Sleep(1000);
            ThirdEvent(this, new EventArgs());
        }
    }
    

    首先,您需要将这些事件“转换”为Observable

    var foo = new Foo();
    var firstWatcher = Observable.FromEventPattern(foo, "FirstEvent");
    var secondWatcher = Observable.FromEventPattern(foo, "SecondEvent");
    var thirdWatcher = Observable.FromEventPattern(foo, "ThirdEvent");
    

    现在您需要“仅在所有这些都触发​​时触发”选择器,即CombineLatest

    var allDone = Observable.CombineLatest(firstWatcher, secondWatcher, thirdWatcher);
    

    并进行测试:

    using(allDone.Subscribe(_ => Console.WriteLine("Boop! You sunk my battleship!")))
    {
        foo.DoIt();
    }    
    

    替代“测试工具”:

    var foo = new Foo();
    var firstWatcher = Observable.FromEventPattern(foo, "FirstEvent");
    var secondWatcher = Observable.FromEventPattern(foo, "SecondEvent");
    var thirdWatcher = Observable.FromEventPattern(foo, "ThirdEvent");
    
    var allDone = Observable.CombineLatest(firstWatcher, secondWatcher, thirdWatcher);
    
    // keep a handle on the subscription            
    IDisposable subscription = null;
    
    // to prevent premature exiting...
    var blocker = new ManualResetEvent(false);
    
    // explicit subscribe
    subscription = allDone.Subscribe(
        whoCares => 
        {
            Console.WriteLine("BOOM! We're done!");
            // always clean up after yourself
            if(subscription != null)
            {
                subscription.Dispose();
            }
            // it's ok, we can quit now
            blocker.Set();
        });
    
    foo.DoIt();
    
    // Wait until it's clear to go ahead...
    blocker.WaitOne();
    

    【讨论】:

    • 感谢 JerKimball 的帮助。很有用。如果事件是异步的,例如,如果我们调用 FireOne|Two|Three 并且这些事件在进行一些异步调用后立即返回而不触发事件呢?我对我的事件进行了尝试,看起来 using(allDone...) 将在事件完成之前退出,因此不会调用 allDone 处理程序(除非我做错了什么)。
    • 例如,如果 public void FireOne() 包含int msDelay = 8000; Task task = Task.Factory.StartNew(() => Thread.Sleep(msDelay)) .ContinueWith((Task starter) => { Console.WriteLine("EVENT 1"); FirstEvent(this, new EventArgs()); }, TaskContinuationOptions.LongRunning);,您是否有一个示例说明如何使您当前的示例工作
    • 实际发生的情况是,在我的蹩脚的小示例线束中,程序在事件触发之前“结束” - 更重要的是,在 allDone.Subscribe 中创建的订阅正在被处理。在using 的右大括号之前放置一个Console.ReadLineThread.Sleep(10000) 或任何你想要的东西(或者,存储它创建的IDisposable 并自行处理)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多