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

时间:2016-03-22 19:38:07

标签: c# .net system.reactive

我正在使用RX 2.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来生成数字,因此它们按顺序运行这一事实也是一个谜。

即使我将NewThreadScheduler替换为TaskPoolSchedulerThreadPoolScheduler,我仍然会得到一个帖子,更有趣的部分是,在这两种情况下Thread.CurrentThread.IsThreadPoolThread都是{{ 1}}。

当我看到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; } ,为什么ThreadPool.QueueUserWorkItem...

我在这里缺少什么?

3 个答案:

答案 0 :(得分:1)

首先,Rx有行为合约。它一次只能通过观察者处理单个值。如果多个观察者附加到一个可观察者(通常只有热观察者),则在生成下一个值之前,每个观察者一次运行一个值。

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

其次,至于为什么他们在同一个线程上运行。这基本上是基于上述行为合同优化绩效的结果。启动新线程需要时间和资源,因此Rx团队尽可能地重用线程以避免开销。

应该很清楚,在一个线程上运行的observable可能比它的观察者可以处理它们更快地产生值。如果是这样,他们就排队了。因此,对于启动新线程以进行观察的任何调度程序,逻辑就是这样 - 如果观察者完成处理值,则当前运行的线程检查队列以查看是否有另一个值要处理,如果有的话处理它 - 不需要新的线程。如果没有,那么线程就会结束或返回到/* Creating Sport table */ CREATE TABLE Sport( SportId INT NOT NULL AUTO_INCREMENT, SportName VARCHAR(20) NOT NULL, Description VARCHAR(200) NOT NULL, PRIMARY KEY(SportId) ); /* Inserting values for Sport table */ INSERT INTO Sport(SportName,Description) VALUES('Agility','This is a fun dog sport which a handler directs a dog through a course of obstacles. Body motion, speed, hand signals are used by the handler to direct the dog.'), ('Flyball','Energetic and exciting sport which is also called drag racing for dogs'), ('Disc Dog','This sport is also known as Frisbee dog. The sport involves the bond between the owner and dog, by letting them to work together'), ('Dog Diving','This sport involves loving run, jumping, and swimming. This is a very popular sport nowadays'), ('Cani-Cross','This is a sport event which running off-road with dogs. It is an useful sport which keeps the owner and the dog fit by a physical workout and directional commands.'); /* Creating UserSport table */ CREATE TABLE UserSport( UserId INT NOT NULL, SportId INT NOT NULL, PRIMARY KEY(UserId,SportId), FOREIGN KEY(UserId) REFERENCES User(UserId) ON DELETE CASCADE, FOREIGN KEY(SportId) REFERENCES Sport(SportId) ON DELETE CASCADE ); /* Inserting values for UserSport table */ INSERT INTO UserSport(UserId,SportId) VALUES(1,2), (1,4), (1,3), (1,5); /* Creating Equipment table */ CREATE TABLE Equipment( EqId INT NOT NULL AUTO_INCREMENT, EqName VARCHAR(40) NOT NULL, Price DECIMAL(9,2) NOT NULL, SportId INT NOT NULL, PRIMARY KEY(EqId), FOREIGN KEY(SportId) REFERENCES Sport(SportId) ON DELETE CASCADE ); /* Inserting values for Equipment table */ INSERT INTO Equipment(EqName,Price,SportId) VALUES('Wing Jump',6700,1), ('Tire',4500,1), ('Open Tunnel',9000,1), ('Chute',3000,1), ('Boomerang Flyball Box',6300,2), ('Training Box',4200,2), ('Flyball Jumps',7800,2), ('Can Am Flyball Box',3100,2), ('Flyball Harness',1800,2), ('Balance Disc',2000,3), ('Training Punches',1000,3), ('Retrieve Water Ring',4500,4), ('Dive Jacket',3000,4), ('Aqua Floating Toy',1500,4), ('Cani-Cross Harness',4000,5), ('PawZ Dog Boots',1000,5), ('Custom Shoulder Harness',3000,5); /* Creating User table */ CREATE TABLE User( UserId INT NOT NULL AUTO_INCREMENT, Title VARCHAR(5) NOT NULL, FName VARCHAR(20) NOT NULL, LName VARCHAR(20) NOT NULL, Email VARCHAR(30) NOT NULL UNIQUE, Phone VARCHAR(15), Address VARCHAR(30) NOT NULL, Password VARCHAR(20) NOT NULL UNIQUE, PRIMARY KEY(UserId) ); /* Inserting values for User table */ INSERT INTO User(Title,FName,LName,Email,Phone,Address,Password) VALUES('Mr.','Raveen','Chandra','admin@dogsport.com','0778547079','Ambalangoda','123'), ('Miss','Bisma','Ishani','bisma@gmail.com','0785234123','Colombo','456'), ('Dr','Shantha','Kumara','shantha@yahoo.com','0912374392','Ambalangoda','789'), ('Ms.','Madusha','Perera','perera@yahoo.com','0782361490','Galle','3235'); 等,所以当一个新的值可用时,该线程就会消失,并且需要一个替代方案。 TaskPool旋转了一个。 NewThreadSchedulerTaskPoolScheduler等获得一个

因此,这归结为一个简单的优化,以便在值排队等待顺序处理时加快处理速度。

在我的测试中,我无法创建一个可观察的样本,在使用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); } (单个不变的线程调度程序)并将调度交给它。难怪线程没有改变。

答案 1 :(得分:1)

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

  

... TaskPool和CLR ThreadPool调度程序都支持   长期运营。前者通过这样做   TaskCreationOptions.LongRunning(真的 - 在今天&#39; s   TPL的实现 - 相当于创建一个新线程);该   后者调用NewThread来达到同样的效果。重要的是什么   超过所使用的特定类型的调度程序是实现的效果。   在这种特殊情况下,人们期望引入引起的异步   额外的并发性。

还接着说:

  

如果你真的想在任何一个下面有一个线程池线程   环境(例如,强制执行全局最大并行度)   为您的应用程序 - 但请记住像   TaskCreationOptions.LongRunning是绕过它的通配符   机制),您必须应用 DisableOptimizations 扩展名   ThreadPoolScheduler实例的方法使其回退   递归行为。请注意,您可以传入所需的接口   禁用(指定none表示禁用所有优化),在此处   case typeof(ISchedulerLongRunning)就足够了。

答案 2 :(得分:1)

根据我上面的评论,看起来你正在尝试使用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

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