Silverlight Task.WaitAll使用Rx

时间:2011-07-12 20:27:47

标签: multithreading silverlight system.reactive

我想同时调用两个Web服务并在完成两个Web服务时处理响应。我使用Rx的Observable.FromAsyncPattern方法调用webservices。同时订阅多个IObservable的正确方法是什么?

我尝试过使用Zip,但它似乎并不是同时启动,只是在收到第一个结果后才开始。 修改 这是一个Zip的演示或其他一些解决我的问题的解决方案 -

class Program
{
    static void Main(string[] args)
    {

        var observable1 = Observable.Create<int>(i =>
            {
                Console.WriteLine("starting 1");
                System.Threading.Thread.Sleep(2000);
                Console.WriteLine("done sleeping 1");
                i.OnNext(1);
                i.OnCompleted();
                return () => { };
            });
        var observable2 = Observable.Create<int>(i =>
        {
            Console.WriteLine("starting 2");
            System.Threading.Thread.Sleep(4000);
            Console.WriteLine("done sleeping 2");
            i.OnNext(1);
            i.OnCompleted();
            return () => { };
        });

        var m = observable1.Zip(observable2, (a, b) => new { a, b });

        var n = Observable.Merge(Scheduler.ThreadPool,
            observable1, observable2);

        var o = Observable.When(observable1.And(observable2).Then((a, b) => new { a, b }));


        m.Subscribe(
            (i) => Console.WriteLine(i),
            () => Console.WriteLine("finished"));

        Console.Read();
    }
}

结果:

starting 1
done sleeping 1
starting 2
done sleeping 2
{ a = 1, b = 1 }
finished

期望的结果:

starting 1
starting 2
done sleeping 1
done sleeping 2
{ a = 1, b = 1 }
finished

5 个答案:

答案 0 :(得分:2)

使用Zip扩展方法是这里的简单答案。

如果您有几个典型的异步调用(假设有单个参数):

Func<X1, IObservable<X2>> callX = Observable.FromAsyncPattern<X1, X2>(...);
Func<Y1, IObservable<Y2>> callY = Observable.FromAsyncPattern<Y1, Y2>(...);

然后你可以同时调用它们并在完成两者之后处理返回值:

callX(x1).Zip(callY(y1), (x2, y2) =>
{
    ...
});

答案 1 :(得分:1)

Rx Joins提供解决方案。

Observable.And

  

当两个可观察序列都具有可用值时匹配。

演示:

var xsL = Observable.Return(1);
var xsR = Observable.Return(2);
Observable<int> both =  Observable.When(xsL.And(xsR).Then((a,b) => a + b));

答案 2 :(得分:0)

Zip是正确的答案:

Observable.Zip(someWebService(), otherWebService(), (some, other) => new { some, other })
    .Subscribe(someAndOther => {
        Console.WriteLine(Some is {0}, and Other is {1}", someAndOther.some, someAndOther.other);
    });

假设someWebService是一个签名如下的方法:

IObservable<SomeClass> someWebService()

如果Zip没有按照您的意愿行事,那么您调用Web服务的方式就会出现问题......

此外,对于一般情况,如果您想知道一堆Observable何时全部终止,您可以使用Aggregate伪造它:

Observable.Merge(
    observable1.Select(_ => Unit.Default),
    observable2.Select(_ => Unit.Default),
    observable3.Select(_ => Unit.Default),
    observable4.Select(_ => Unit.Default))
.Aggregate(Unit.Default, (acc, _) => acc)
.Subscribe(_ => Console.WriteLine("They all finished!");

然后,如果要阻止,可以在结尾处抛出First()而不是Subscribe。

答案 3 :(得分:0)

Observable.Create不是Observable.FromAsyncPattern的正确模型; Zip,Merge和When提议的解决方案适用于Observable.FromAsyncPattern。

Observable.FromAsyncPattern添加并发性(来自底层的BeginXXX调用或使用AsyncSubject(Scheduler.ThreadPool)),而Observable.Create默认情况下将调度当前线程。

Observable.FromAsyncPattern的更好模型是:

var observable1 = Observable.Create<int>(i =>
{
    return Scheduler.ThreadPool.Schedule(() =>
    {
        Console.WriteLine("starting 1");
        System.Threading.Thread.Sleep(4000);
        Console.WriteLine("done sleeping 1");
        i.OnNext(1);
        i.OnCompleted();
    });
});

答案 4 :(得分:0)

当我在寻找别的东西时,我偶然发现了这个问题。似乎有一些混淆,为什么不能同时订阅observable2。答案很简单:默认情况下Rx不是多线程的。由您来管理线程/调度,它将有助于并发。 @Enigmativity对此不以为然,但我认为这需要更深入的解释。

具体;我们有以下(汇总)代码

var observable1 = Observable.Create<int>(i =>
    {
        System.Threading.Thread.Sleep(2000);
        i.OnNext(1);
        i.OnCompleted();
        return () => { };
    });
var observable2 = Observable.Create<int>(i =>
{
    System.Threading.Thread.Sleep(4000);
    i.OnNext(1);
    i.OnCompleted();
    return () => { };
});

var m = observable1.Zip(observable2, (a, b) => new { a, b });
m.Subscribe(Console.WriteLine);

如果我们逐步完成这一点,那么问题就显而易见了。

  1. 我们使用Create factory method
  2. 声明了2个可观察序列
  3. 我们用Zip方法
  4. 组成2个序列
  5. 我们订阅了复合序列(m)
  6. 然后调用提供给observable1的Create方法的委托。
  7. 我们进入代表并立即睡眠2秒。请注意,我们在这里没有更改过线程。发布的代码是单线程的。
  8. 我们继续委托和OnNext的值为1,然后我们完成序列
  9. 仍然在同一个线程上(因为这是单线程)我们然后订阅Observable2并进入其委托
  10. 我们睡了4秒
  11. We onNext 1.这会被推送到一直等待第二个序列产生值的Zip运算符。
  12. 调用zip resultSelector函数并创建a = 1,b = 1的Anon类型并将其推送到控制台(通过Subscribe方法)
  13. 序列完成。
  14. 好的,这显然永远无法奏效。但幸运的是,这只是@foson用来描述他们问题的一个例子。具有讽刺意味的是,如果他们使用FromAsyncPattern,它会为他们的代码引入一些并发性,它会起作用。

    使用延迟2s和4s来演示Zip的正确方法是成为并发。您可以通过在当前线程上调度OnNext或使用另一个线程/计时器来执行此操作。

    var observable1 = Observable.Timer(TimeSpan.FromSeconds(2));
    var observable2 = Observable.Timer(TimeSpan.FromSeconds(4));
    
    var zipped = observable1.Zip(observable2, (a, b) => new { a, b });
    zipped.Subscribe(Console.WriteLine);
    

    这里我们使用方便的Observable.Timer工厂。它将创建一个序列,该序列在订阅时从给定时间段发布值0,然后完成。如果您对如何安排计时器有偏好,那么您也可以提供可选的计划程序,例如

    var observable1 = Observable.Timer(TimeSpan.FromSeconds(2), Scheduler.ThreadPool);
    var observable2 = Observable.Timer(TimeSpan.FromSeconds(4), Scheduler.TaskPool);
    

    默认调度程序(在编写.NET 4.0库的v.1.0.10621.0时)是Scheduler.ThreadPool。

    您可以在我的Rx系列简介中找到有关计划的更多信息:

    http://leecampbell.blogspot.com/2010/08/reactive-extensions-for-net.html 特别是调度和线程发布

    http://leecampbell.blogspot.com/2010/06/rx-part-6-scheduling-and-threading.html

    我希望通过原帖来澄清问题。