RX:如何等待订户完成?

时间:2019-05-06 10:08:00

标签: c# language-agnostic observable reactive-programming system.reactive

我有一个生产者,可以从Rest API下载页面中的数据,还有几个处理页面的消费者(例如,将它们加载到数据库中)。

我希望生产者和使用者并行工作,这意味着生产者在下载下一个页面之前不应该等待页面被消耗。每个使用者都需要按顺序处理页面。

下载所有页面后,主线程应等待所有使用者完成工作(因为消耗可能比生产花费的时间更长)。

我当前的方法如下:

我创建了一个可观察的页面,可以下载页面,该页面在附加了消费者订阅者后立即开始。我配置了订阅者以观察 自己的线程以并行执行。

C#中的代码:

IEnumerable<Page> getPages = produce();
var observable = getPages.ToObservable().Publish();

observable
   .ObserveOn(NewThreadScheduler.Default)
   .Subscribe(page => consume1(page));

observable
   .ObserveOn(NewThreadScheduler.Default)
   .Subscribe(page => consume2(page));

observable.Connect();

此实现的问题是主线程可能在处理所有页面和应用程序停止之前完成。

如何使用RX实现呢?

谢谢!

编辑:

还尝试了以下方法(从答案中得出):

static void Main(string[] args)
{
    var getPages = Enumerable.Range(0, 10);

    var els1 = new EventLoopScheduler();
    var els2 = new EventLoopScheduler();

    var observable =
        getPages
            .ToObservable()
            .Publish(ps =>
                Observable
                    .Merge(
                        ps.Select(p => Observable.Start(() => consume1(p), els1)),
                        ps.Select(p => Observable.Start(() => consume2(p), els2))));

    observable.Wait();
}

public static void consume1(int p)
{
    Console.WriteLine($"1:{p}");
    Thread.Sleep(200);
}

public static void consume2(int p)
{
    Console.WriteLine($"2:{p}");
    Thread.Sleep(100);
}

observable.Wait()在基础可枚举完成产生值后立即返回。输出为:

1:0
2:0

只需证明,如果我们将getPages替换为:

var getPages = Enumerable.Range(0, 10)
    .Select(i =>
    {
        Console.WriteLine($"Produced {i}");
        Thread.Sleep(30);
        return i;
    });

然后输出为:

Produced 0
Produced 1
1:0
2:0
Produced 2
Produced 3
Produced 4
2:1
Produced 5
Produced 6
Produced 7
1:1
2:2
Produced 8
Produced 9

1 个答案:

答案 0 :(得分:2)

我认为这可以满足您的要求

var els1 = new EventLoopScheduler();
var els2 = new EventLoopScheduler();

var observable =
    getPages
        .ToObservable()
        .Publish(ps =>
            Observable
                .Merge(
                    ps.SelectMany(p => Observable.Start(() => consume1(p), els1)),
                    ps.SelectMany(p => Observable.Start(() => consume2(p), els2))));

我编写了以下测试代码:

var getPages = Enumerable.Range(0, 10);

var els1 = new EventLoopScheduler();
var els2 = new EventLoopScheduler();

var observable =
    getPages
        .ToObservable()
        .Publish(ps =>
            Observable
                .Merge(
                    ps.SelectMany(p => Observable.Start(() => consume1(p), els1)),
                    ps.SelectMany(p => Observable.Start(() => consume2(p), els2))));

observable.Wait();

public void consume1(int p)
{
    Console.WriteLine($"1:{p}");
    Thread.Sleep(200);
}

public void consume2(int p)
{
    Console.WriteLine($"2:{p}");
    Thread.Sleep(100);
}

我得到以下输出:

1:0
2:0
2:1
1:1
2:2
2:3
1:2
2:4
2:5
1:3
2:6
2:7
1:4
2:8
2:9
1:5
1:6
1:7
1:8
1:9

完成EventLoopScheduler个实例后,应在它们上调用.Dispose()以关闭线程。