C# 为什么RX没有在新线程上运行处理程序?

C# 为什么RX没有在新线程上运行处理程序?,c#,.net,system.reactive,C#,.net,System.reactive,我正在使用RX2.2.5 在下面的示例中,我希望处理程序(Subscribe()的委托)在一个新线程上运行,但是在运行应用程序时,所有10个数字都在同一个线程上一个接一个地使用 Console.WriteLine("Main Thread: {0}", Thread.CurrentThread.ManagedThreadId); var source = Observable.Range(1, 10, TaskPoolScheduler.Default) .ObserveOn(NewT

我正在使用RX2.2.5

在下面的示例中,我希望处理程序(Subscribe()的委托)在一个新线程上运行,但是在运行应用程序时,所有10个数字都在同一个线程上一个接一个地使用

Console.WriteLine("Main Thread: {0}", Thread.CurrentThread.ManagedThreadId);

var source = Observable.Range(1, 10, TaskPoolScheduler.Default)
    .ObserveOn(NewThreadScheduler.Default)
    .Subscribe(n =>
    {
        Thread.Sleep(1000);
        Console.WriteLine(
            "Value: {0} Thread: {1} IsPool: {2}", 
            n, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
    });
输出:

Main Thread: 1
Value: 1  on Thread: 4 IsPool: False
Value: 2  on Thread: 4 IsPool: False
Value: 3  on Thread: 4 IsPool: False
Value: 4  on Thread: 4 IsPool: False
Value: 5  on Thread: 4 IsPool: False
Value: 6  on Thread: 4 IsPool: False
Value: 7  on Thread: 4 IsPool: False
Value: 8  on Thread: 4 IsPool: False
Value: 9  on Thread: 4 IsPool: False
Value: 10 on Thread: 4 IsPool: False
它们按顺序运行的事实也是一个谜,因为我正在使用
TaskPoolScheduler
生成数字

即使我用
TaskPoolScheduler
ThreadPoolScheduler
替换
NewThreadScheduler
,我仍然会得到一个线程,更有趣的是在这两种情况下
thread.CurrentThread.IsThreadPoolThread
都是
False

我无法解释这种行为,因为当我查看
ThreadPoolScheduler
时,我看到:

public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
{
  if (action == null)
    throw new ArgumentNullException("action");
  SingleAssignmentDisposable d = new SingleAssignmentDisposable();
  ThreadPool.QueueUserWorkItem((WaitCallback) (_ =>
  {
    if (d.IsDisposed)
      return;
    d.Disposable = action((IScheduler) this, state);
  }), (object) null);
  return (IDisposable) d;
}
public override IDisposable时间表(TState state,Func action)
{
if(action==null)
抛出新的异常(“操作”);
SingleAssignmentDispossible d=新的SingleAssignmentDispossible();
ThreadPool.QueueUserWorkItem((WaitCallback)(\uUserWorkItem=>
{
如果(d.IsDisposed)
返回;
d、 一次性=操作((isScheduler)此状态);
}),(对象)空);
返回(IDisposable)d;
}
我可以清楚地看到
ThreadPool.QueueUserWorkItem…
那么为什么
IsPool==False


我在这里遗漏了什么?

首先,Rx有一个行为契约。它一次只能通过观察者处理单个值。如果一个可观察对象(通常仅为热可观察对象)上有多个观察者,则在产生下一个值之前,每个观察者每次运行一个值

这就解释了为什么你的价值观会一个接一个地产生

Console.WriteLine("Main Thread: {0}", Thread.CurrentThread.ManagedThreadId);

var source = Observable.Range(1, 10, TaskPoolScheduler.Default)
    .ObserveOn(NewThreadScheduler.Default)
    .Subscribe(n =>
    {
        Thread.Sleep(1000);
        Console.WriteLine(
            "Value: {0} Thread: {1} IsPool: {2}", 
            n, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
    });
第二,为什么它们运行在同一个线程上。这基本上是基于上述行为契约优化性能的结果。启动新线程需要时间和资源,因此Rx团队尽可能重用线程以避免开销

应该清楚的是,在一个线程上运行的可观察线程可能会比其观察者处理它们的速度更快地生成值。如果是这样,他们就会排队。因此,对于任何启动新线程以观察的调度程序来说,逻辑是这样的——如果观察程序处理完一个值,那么当前正在运行的线程将检查队列以查看是否有另一个值要处理,如果有,则处理它——不需要新线程。如果没有,则线程将结束或返回到
任务池
等,因此当新值可用时,该线程将消失,需要一个替代值。
NewThreadScheduler
向上旋转一个线程。
TaskPoolScheduler
TaskPool
等中获取一个

因此,这归结为一个简单的优化,当值排队进行顺序处理时,可以加快处理速度


在我的测试中,当使用
NewThreadScheduler
时,我无法创建一个可观察的样本,为每个新值使用新线程,但后来我在
NewThreadScheduler
源代码中发现了这一点:

public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
  if (action == null)
    throw new ArgumentNullException("action");
  EventLoopScheduler eventLoopScheduler = new EventLoopScheduler(this._threadFactory);
  eventLoopScheduler.ExitIfEmpty = true;
  return eventLoopScheduler.Schedule<TState>(state, dueTime, action);
}
public override IDisposable时间表(TState状态、TimeSpan dueTime、Func操作)
{
if(action==null)
抛出新的异常(“操作”);
EventLoopScheduler EventLoopScheduler=新的EventLoopScheduler(此.\u threadFactory);
eventLoopScheduler.ExitIfEmpty=true;
返回eventLoopScheduler.Schedule(状态、时间、操作);
}

因此,在引擎盖下,它正在创建一个新的
EventLoopScheduler
(单线程不更改调度程序),并将调度交给这个。难怪线程没有改变。

显然这是设计的!正如所解释的那样:

…TaskPool和CLR线程池调度程序都支持 长期运行的操作。前者是通过 TaskCreationOptions.LongRunning(在今天的 第三方物流的实施——相当于创建一个新的线程);这个 后者调用NewThread以达到相同的效果。重要的是什么 所达到的效果不仅仅是所使用的特定类型的调度器。 在这种特殊情况下,需要引入异步 额外的并发性

它还说:

如果您真的想在任何 环境(例如,强制执行全局最大并行度 对于你的应用程序-尽管要记住 TaskCreationOptions.LongRunning是通配符,用于绕过此选项 机制),您必须应用DisableOptimizations扩展 方法调用ThreadPoolScheduler实例,使其返回到 递归行为。请注意,您可以传入您想要的接口 禁用(指定none意味着禁用所有优化),在这种情况下 case typeof(IsSchedulerLongRunning)就足够了


根据我上面的评论,看起来您正在尝试使用Rx(用于查询和组合可观察数据序列的库)作为并行计算库

我认为要看到您期望的结果,您可以将您的查询修改为这样

Console.WriteLine("Main Thread: {0}", Thread.CurrentThread.ManagedThreadId);

var source = Observable.Range(1, 10, TaskPoolScheduler.Default)
    .SelectMany(i=>Observable.Start(()=>i, NewThreadScheduler.Default))
    //.ObserveOn(NewThreadScheduler.Default)
    .Subscribe(n =>
    {
        Thread.Sleep(1000);
        Console.WriteLine(
            "Value: {0} Thread: {1} IsPool: {2}",
            n, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
    });
输出:

Main Thread: 11
Value: 1 Thread: 15 IsPool: False
Value: 4 Thread: 21 IsPool: False
Value: 2 Thread: 14 IsPool: False
Value: 3 Thread: 13 IsPool: False
Value: 5 Thread: 21 IsPool: False
Value: 6 Thread: 21 IsPool: False
Value: 7 Thread: 21 IsPool: False
Value: 8 Thread: 21 IsPool: False
Value: 9 Thread: 21 IsPool: False
Value: 10 Thread: 21 IsPool: False

注意,我们在这里看到了多个线程,但也注意到我们现在得到了无序的值(显然,因为我们引入了我们不再控制的并发性)。因此,这是一种选择毒药(或适当的库)的情况。

基本上,
ObserveOn
不会按照您的想法工作,向操作员引入
调度程序
会告诉操作员应该在哪里发生并发。此外,在Rx中,通知始终是序列化的。让我们等待一位专家写出一个明确的答案:)Rx的行为方式与我在这里预期的一样。你到底想做什么?看起来你正试图使用Rx来执行并行处理(这并不是它真正的设计目的)。@Lee,请看我在你回答下面的评论。谢谢你的详细解释。我仍然对她有点困惑