What's the rationale behind how CurrentThreadScheduler.ScheduleRequired behaves?


  • I'm a little perplexed by the value returned by CurrentThreadScheduler.ScheduleRequired. It seems backwards.

    I think I understand the basic idea behind this scheduler - it runs things on the calling thread, but rather than running work immediately it can put things on a thread-local queue, the idea being that it a work item in progress decides to schedule another work item, it doesn't run it right away, and instead can put it on the queue to be run when the current item completes.

    I've read this: and also this

    Here's the part that confuses me:

    The ScheduleRequired property returns true if no work is in progress, and false it work is in progress. Or more specifically, if a queue has already been setup on the current thread because a Trampoline is in progress, it will tell you that no, there is no need to go via the scheduler right now. However, if there is no work queue in existence right now (which appears to mean that there is no trampoline in progress, and I think that would mean that the thread is not currently in the middle of executing a scheduled work item through this scheduler, it will tell you that yes, you do need to go through the scheduler.

    Shouldn't it be the other way around? If I write this:

        static void Main(string[] args)

    Should that really print True? (It does on the current Rx 2.0 beta.) Moreover, if a work item is in progress, isn't that exactly the scenario in which you'd want to post work via the queue instead of running it immediately? So I'd expect this:

        static void Main(string[] args)
            CurrentThreadScheduler.Instance.Schedule(() =>

    to print True. In fact it prints False.

    I'm wondering if this is correct. As far as I can tell, the only things in Rx itself that use this are ObservableBase.Subscribe and Producer.Subscribe. Then again, lots of things go through those, so if this is upside down, you'd think it would have shown up by now...

    I was wondering if the intent of ScheduleRequired is in fact to say "There's no queue right now, so if you think we need one you'd better call Schedule to set one up." Except that doesn't really seem to make sense - CurrentThreadScheduler's Schedule method seems to create the Trampoline if it's not already there, so the only interesting question for a consumer of this scheduler would not seem to be "Should I try to get it to create the trampoline?" but "Do I need to go via the trampoline, or can I make a synchronous call."

    So I guess I've misunderstood something somewhere.

    • 편집됨 IanG 2012년 3월 26일 월요일 오후 9:59 Fixed a sentence that got things exactly backwards...ironic, under the circumstances
    2012년 3월 26일 월요일 오후 9:57

모든 응답

  • Hi, 

    Perhaps it's just a bad name.  I suspect that its semantics are actually: NewTrampolineRequired.  If the trampoline is active, then it should return false; otherwise, if the trampoline is not active, then it should return true.

    If it's only being used by Subscribe, then perhaps its purpose is to indicate whether Subscribe must inject a trampoline automatically.  Without it, there would be redundancy creating a trampoline for every call to Subscribe.  A query needs at least one trampoline on the first call to Subscribe for sequences that begin on CurrentThreadScheduler so that they are asynchronous.

    - Dave

    • 편집됨 Dave Sexton 2012년 3월 26일 월요일 오후 11:24 Clarification to last sentence
    2012년 3월 26일 월요일 오후 11:22
  • I certainly find the current name confusing. :)

    By observation, ScheduleRequired could be renamed TrampolineNotCurrentlyActive, which would better describe its behaviour. What I'm having trouble understanding is why TrampolineNotCurrentlyActive would necessarily be synonymous with your proposed name NewTrampolineRequired. (Particularly when you bear in mind that the Subscribe method that uses this property deliberately bypasses the trampoline if there is one.) What is or is not required is surely a decision for whichever code looks at the property.

    I think I was misled by the fact that the two bits of Rx that use this property seem to use it for more than just deciding whether to create a trampoline: The decision they make seems to be between create and use new trampoline and bypass trampoline entirely. But I've realised (see below) that this apparent bypass/don't bypass decision is illusory - the true goal here seems to be to run the Subscribe work immediately.

    Regarding your second paragraph, I don't think that's correct, because even if this ScheduleRequired were hardwired to true, Subscribe would not end up creating multiple trampolines. Subscribe only creates a trampoline indirectly, as a result of calling Schedule (which it only uses is ScheduleRequired is true). And if you call Schedule when a trampoline is already running, you don't get a second trampoline. Your work item is just added to the queue of the existing trampoline. (In fact, as I understand it, that's the whole point of the trampoline.)

    So although I can see that a "set up a trampoline if we don't already have one" step could be useful, to ensure that any further work items scheduled while the Subscribe is in progress get queued rather than running immediately, I don't quite understand why it's OK for Subscribe to bypass the trampoline's queue and run synchronously in the situation. Because that appears to be the logic: set up a queue if there isn't one, and jump the queue if there is.

    And now I've thought about it some more, I've realised that the fact that Subscribe ever schedules its work item is misleading me here - I originally thought it was doing that because it wanted that work to execute via the scheduler (which made no sense, because the only thing CurrentThreadScheduler provides is a thread-local queue, and Subscribe only used that in scenarios where it got to be the first thing in the queue, thus running immediately, just like it would if it didn't schedule). But I've now realised that executing the subscription via a scheduler is done purely for the side effect of making sure that any further work items queued while the subscribe work item runs get queued.

    In short, it's not about running the subscription callback via a scheduler. It's about how work scheduled while that callback is in progress gets handled. (And if there were some way to set up a trampoline without calling Schedule, perhaps it would do that instead.)

    So if the requirements are as follows:

    1. The subscription callback must always execute synchronously, regardless of whether the CurrentThreadScheduler currently has a trampoline and queue.
    2. Any work scheduled via CurrentThreadScheduler while the subscription callback is in progress must not run immediately, and must instead be queued and run by the trampoline.

    then the behaviour and implementation makes sense to me, even if the name ScheduleRequired does not... But if I think of it as ScheduleRequiredIfYouWantDescendentWorkItemsToBeQueued, then perhaps it makes sense.

    So that first requirement is the key here. The reason it bypasses the scheduler when a queue already exists is not to avoid creating a second trampoline, it's to jump the queue. If a queue doesn't yet exist, it's OK to go via the schedule because it will be at the head of the queue anyway, removing any need to jump it.

    2012년 3월 27일 화요일 오전 8:50
  • Hi,

    Looking at the Subscribe method in Reflector, my guess was in fact wrong.   Your analysis is correct.

    Actually, the first call to Subscribe creates the trampoline so that it may assign its disposable before any observer executes OnNext over the CurrentThreadScheduler.  This enables single-threaded cancellation; e.g., in one of the links that you posted, I described how the Take operator is able to cancel a recursive query due to the trampoline.  If the outer-most Subscribe isn't the first to create the trampoline, then its disposable wouldn't be assigned and thus it couldn't be canceled by inner operators.

    - Dave

    2012년 3월 27일 화요일 오후 1:01