2

Is there some version of Observable.Delay or Observable.Buffer that doesn't use a new thread for its timer? Perhaps with less precision..

I have a scenario where I need to call Observable.Delay on an observable that produces several thousands messages per second which create a lot of threads..

Thank you.

James World
  • 29,019
  • 9
  • 86
  • 120
fsl
  • 831
  • 1
  • 11
  • 24
  • That doesn't sound very likely. Wouldn't that pretty much amount to doing Thread.Sleep? It seems to me that you shouldn't really be thinking about that in reactive programming anyway, what are you trying to do? – Luaan Jan 07 '15 at 15:46
  • I'm using a GroupByUntil that groups by minute and the durationSelector is doing a Observable.Return(1).Delay(TimeSpan.FromMinutes(2)). In other words, a group is emitted after two minutes.. – fsl Jan 07 '15 at 15:51
  • Okay, but why do you care where the timer callback happens? – Luaan Jan 07 '15 at 15:52

1 Answers1

5

If you want to limit the number of threads then you only need to think about the scheduler you use, this has nothing to do with the number of timers created. Time-based operations schedule actions with due times and it's the scheduler that decides how to dispatch events at the correct time - Rx is smart about how many timers actually get created and this mechanism is dependent on the scheduler used.

The default scheduler on most platforms used by time-based operators (Scheduler.Default) will use the Task pool to obtain the thread to dispatch an event that was scheduled at a future time and this is why you will generally see different threads being used to dispatch events.

One way to control threads is to use an EventLoopScheduler and ensure you specify this when using time-based operations. This will dispatch all it's events on the same thread. For example:

var scheduler = new EventLoopScheduler();

Observable.Return(1)
          .Delay(TimeSpan.FromSeconds(4), scheduler)
          .Subscribe(x => Console.WriteLine(x.ToString() + ": "
              + Thread.CurrentThread.ManagedThreadId));
Observable.Return(2).Delay(TimeSpan.FromSeconds(2), scheduler)
          .Subscribe(x => Console.WriteLine(x.ToString() + ": "
              + Thread.CurrentThread.ManagedThreadId));

Console.WriteLine("Done.");

Will output something like (ThreadId will vary of course):

Done.
2: 3
1: 3

Whereas if you change the first line to var scheduler = Scheduler.Default; you would see different thread IDs.

The topic of timers in Rx is quite complex and probably a bit too broad for this format - this excellent post covers a lot of the internal details. See the section a fair way down "It's all about time."

James World
  • 29,019
  • 9
  • 86
  • 120
  • 1
    I wonder if it's also worth noting that the OP wrote "which create a lot of threads", which seems to be the primary concern, though the default in Rx is to use pooled threads, as you noted, which amortizes the creation costs; i.e., the thread activation cost per notification is 0. Of course, there's overhead in the context switches, but that's unavoidable if the OP needs concurrency. Ultimately, the best solution is probably to just let Rx call back on pooled threads. – Dave Sexton Jan 07 '15 at 18:12
  • Good stuff. Bear in mind @DaveSexton 's comment as well - it's sound advice. – James World Jan 07 '15 at 22:47