C# 为什么每个观察代理都在新线程上运行

C# 为什么每个观察代理都在新线程上运行,c#,system.reactive,C#,System.reactive,在Rx中,当使用Scheduler.NewThread for ObserveOn方法时,当Rx已经保证OnNext不会重叠时,让每个观察代理(OnNext)在新线程上运行有什么好处。如果每个OnNext都将被一个接一个地调用,那么为什么需要为每个OnNext调用新线程呢 我理解为什么要在订阅和应用程序线程之外的线程上运行观察委托,但在新线程上运行每个观察委托,而它们永远不会并行运行?。。。。对我来说没有意义或者我在这里遗漏了什么 比如说 using System; using System.L

在Rx中,当使用Scheduler.NewThread for ObserveOn方法时,当Rx已经保证OnNext不会重叠时,让每个观察代理(OnNext)在新线程上运行有什么好处。如果每个OnNext都将被一个接一个地调用,那么为什么需要为每个OnNext调用新线程呢

我理解为什么要在订阅和应用程序线程之外的线程上运行观察委托,但在新线程上运行每个观察委托,而它们永远不会并行运行?。。。。对我来说没有意义或者我在这里遗漏了什么

比如说

using System;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;

namespace RxTesting
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Application Thread : {0}", Thread.CurrentThread.ManagedThreadId);

            var numbers = from number in Enumerable.Range(1,10) select Process(number);

            var observableNumbers = numbers.ToObservable()
                .ObserveOn(Scheduler.NewThread)
                .SubscribeOn(Scheduler.NewThread);

            observableNumbers.Subscribe(
                n => Console.WriteLine("Consuming : {0} \t on Thread : {1}", n, Thread.CurrentThread.ManagedThreadId));

            Console.ReadKey();
        }

        private static int Process(int number)
        {
            Thread.Sleep(500);
            Console.WriteLine("Producing : {0} \t on Thread : {1}", number,
                              Thread.CurrentThread.ManagedThreadId);

            return number;
        }
    }
}
上面的代码生成以下结果。请注意,每次消耗都是在一个新线程上完成的

Application Thread : 8
Producing : 1    on Thread : 9
Consuming : 1    on Thread : 10
Producing : 2    on Thread : 9
Consuming : 2    on Thread : 11
Producing : 3    on Thread : 9
Consuming : 3    on Thread : 12
Producing : 4    on Thread : 9
Consuming : 4    on Thread : 13
Producing : 5    on Thread : 9
Consuming : 5    on Thread : 14
Producing : 6    on Thread : 9
Consuming : 6    on Thread : 15
Producing : 7    on Thread : 9
Consuming : 7    on Thread : 16
Producing : 8    on Thread : 9
Consuming : 8    on Thread : 17
Producing : 9    on Thread : 9
Consuming : 9    on Thread : 18
Producing : 10   on Thread : 9
Consuming : 10   on Thread : 19

NewThread调度程序对于长时间运行的订阅服务器很有用。如果未指定任何计划程序,则生产者将被阻止,等待订阅服务器完成。通常,您可以使用Scheduler.ThreadPool,但如果您希望有许多长时间运行的任务,您就不想用它们阻塞线程池(因为它可能被不止一个可观察对象的订阅者使用)

例如,考虑对您的示例进行以下修改。我将延迟移到订户,并添加了主线程何时准备好键盘输入的指示。请注意取消注释newhead行时的区别

using System;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;

namespace RxTesting
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Application Thread : {0}", Thread.CurrentThread.ManagedThreadId);

            var numbers = from number in Enumerable.Range(1, 10) select Process(number);

            var observableNumbers = numbers.ToObservable()
//              .ObserveOn(Scheduler.NewThread)
//              .SubscribeOn(Scheduler.NewThread)
            ;

            observableNumbers.Subscribe(
                n => {
                    Thread.Sleep(500);
                    Console.WriteLine("Consuming : {0} \t on Thread : {1}", n, Thread.CurrentThread.ManagedThreadId);
                });

            Console.WriteLine("Waiting for keyboard");
            Console.ReadKey();
        }

        private static int Process(int number)
        {
            Console.WriteLine("Producing : {0} \t on Thread : {1}", number,
                              Thread.CurrentThread.ManagedThreadId);

            return number;
        }
    }
}

那么为什么Rx不优化为每个订阅者使用相同的线程呢?如果订阅服务器运行时间太长,您需要一个新线程,那么线程创建开销无论如何都是微不足道的。一个例外是,如果大多数订阅者都很短,但有几个是长时间运行的,那么重用同一线程的优化确实会很有用。

我不确定您是否注意到,但是如果消费者比生产者慢(例如,如果您在订阅操作中添加更长的睡眠时间),他们将共享同一线程,因此,这可能是一种确保订阅者在内容发布后立即消费内容的机制

namespace RxTesting
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Application Thread : {0}", Thread.CurrentThread.ManagedThreadId);

            var numbers = from number in Enumerable.Range(1,10) select Process(number);

            var observableNumbers = numbers.ToObservable()
                .ObserveOn(Scheduler.NewThread)
                .SubscribeOn(Scheduler.NewThread);

            observableNumbers.Subscribe(
                n => 
                    {
                        Console.WriteLine("Consuming : {0} \t on Thread : {1}", n, Thread.CurrentThread.ManagedThreadId);
                        Thread.Sleep(600);
                    }
                        );

            Console.ReadKey();
        }

        private static int Process(int number)
        {
            Thread.Sleep(500);
            Console.WriteLine("Producing : {0} \t on Thread : {1}", number,
                              Thread.CurrentThread.ManagedThreadId);

            return number;
        }
    }
}
产出:

Application Thread : 1
Producing : 1    on Thread : 3
Consuming : 1    on Thread : 4
Producing : 2    on Thread : 3
Consuming : 2    on Thread : 4
Producing : 3    on Thread : 3
Consuming : 3    on Thread : 4
Producing : 4    on Thread : 3
Consuming : 4    on Thread : 4
Producing : 5    on Thread : 3
Consuming : 5    on Thread : 4
Producing : 6    on Thread : 3
Consuming : 6    on Thread : 4
Producing : 7    on Thread : 3
Producing : 8    on Thread : 3
Consuming : 7    on Thread : 4
Producing : 9    on Thread : 3
Consuming : 8    on Thread : 4
Producing : 10   on Thread : 3
Consuming : 9    on Thread : 4
Consuming : 10   on Thread : 4

你在哪里看到了让每个观察委托(OnNext)在自己的线程上运行的代码示例?@Robert Harvey:我已经包含了重现问题的示例代码。你是说每次消费都会等待前一次消费完成吗?那没有任何意义。如果您在一个单独的线程上使用每个观察结果,那么该使用可以异步执行,而其他使用则在它们自己的线程上进行。如果您在自己的线程上使用每个OnNext,该线程将立即释放OnNext队列以触发下一个线程。虽然消耗量是按顺序发射的,但它们仍然可以同时处理。是的,这正是我所说的,我最初认为OnNexts将并行发射,但它们没有。这是Rx的一个功能,它保证收集的顺序将得到维护,并且在OnNext for 1完成之前,OnNext for 2不会着火。请参阅+1。我想补充一点,在线程池被其他操作重载的情况下,它允许比线程池更可预测的执行。@Edward:我不理解您的最后一点“如果订阅服务器运行时间太长,您需要一个新线程,那么线程创建开销将是微不足道的”?如果Rx保证按顺序运行每个订阅服务器,那么为每个订阅服务器创建一个新线程有什么意义?@nabeelfarld:没有意义。然而,它可能需要Rx库中的额外代码来在订阅者之间共享线程,因此我猜测,没有人会费心编写优化代码,因为这样做的前提是,它无论如何都不会被注意到。