Copied from https://rx.codeplex.com/workitem/10
Related discussion:
http://social.msdn.microsoft.com/Forums/en-US/rx/thread/1be3a07f-68c2-42a0-b5f5-ec3446f9f514
Add new properties to the Scheduler class (examples provided below) to simplify the choice for users, to hide internal optimization details and to make the semantics of choosing a scheduler reflect the actual underlying concerns of operators.
Furthermore, consider making existing platform-specific schedulers' internal (at least their parameterless constructors) and removing the "Instance" and "Default" factories for them; Alternatively, consider changing the behavior of operators so that when they receive a concrete scheduler no optimizations are applied and the specified scheduler is used directly, and only apply optimizations when a façade scheduler is passed in, avoiding the need for the DisableOptimizations operator.
PROPOSAL FOR COMPLETE LIST OF FACADES AND SCHEDULER CLASSES
NOTE: The schedulers suggested below were given precedence based on implementations for .NET 4.5 only; however, the suggested abstractions would allow Rx to internally choose the best scheduler for the job by considering the current platform.
• Scheduler.Synchronous = ImmediateScheduler
• Scheduler.Asynchronous
With precedence from left to right:
Standard = DispatcherScheduler / ControlScheduler -> SynchronizationContextScheduler -> CurrentThreadScheduler
ISchedulerLongRunning = NewThreadScheduler
ISchedulerPeriodic = DispatcherScheduler / ControlScheduler -> ThreadPoolScheduler
• Scheduler.Concurrent
With precedence from left to right:
Standard = ThreadPoolScheduler
ISchedulerLongRunning = NewThreadScheduler
ISchedulerPeriodic = ThreadPoolScheduler
• ControlScheduler: Useful for when thread-affinity is required; e.g., Scheduler.Asynchronous may choose NewThreadScheduler, so users must be able to call .ObserveOn(ControlScheduler) explicitly at the end of a query if it must notify observers on the UI thread.
• CurrentThreadScheduler: Useful for simple applications and experimentation.
• DispatcherScheduler: Useful for when thread-affinity is required; e.g., Scheduler.Asynchronous may choose NewThreadScheduler, so users must be able to call .ObserveOnDispatcher() explicitly at the end of a query if it must notify observers on the UI thread.
• EventLoopScheduler: Useful for dedicated work queues. Its use implies that a new reference must be passed to more than one operator, thus making it difficult or impossible to hide behind scheduler façades.
• HistoricalScheduler: Useful for instrumentation and diagnostics.
• VirtualTimeScheduler: Useful for testing and experimentation.
• TestScheduler: Useful for testing and experimentation.
davedev wrote Dec 4, 2012 at 4:58 PM
I wonder if EventLoopScheduler could be hidden behind a Scheduler.AsynchronousLoop façade by supporting the following interface on operators:
c#
public interface IObservableOperator<T> : IObservable<T>
{
IDisposable SubscribeSafe(IScheduler schedulerContext)
}
This would enable an upstream operator to receive information about downstream scheduling as kind of a local query context. Before subscribing, and automatically for Observable.Create and ObservableBase
The original topic is almost 2 years old, so I'm not sure how much of this is still a plan. But I find it quite confusing the idea of "Asynchronous" and "Concurrent" names. After all, aren't concurrent schedulers also asynchronous?
But more importantly, there is the issue that thread affinity is a specific UI problem that a lot of people don't have to deal with. Most people are doing web development nowadays, there has been a trend of people moving from desktop apps to web for years, and this will only increase. Windows services also don't care about this. So, having a very generic name like "Asynchronous" associated with a very specific platform issue is strange.
Instead, Asynchornous should point to the current DefaultScheduler scheduler or something that uses Task/ThreadPool. It makes more sense as a general purpose "asynchronous scheduler".
For windows apps, it should be named "Scheduler.WindowsUI", "Scheduler.UIContext", or "Scheduler.UIThread", or any other better name that tells what this scheduler is doing.
aren't concurrent schedulers also asynchronous?
Yes, but not all async schedulers are concurrent; e.g., DispatcherScheduler and CurrentThreadScheduler. They use single-threaded asynchrony.
The term "asynchronous" is an umbrella term that covers single-threaded asynchrony (a.k.a., cooperative multitasking), concurrency (true parallelism) and even synchrony in the limit.
[snip] So, having a very generic name like "Asynchronous" associated with a very specific platform issue is strange.
The point of the term "Asynchronous" is that it's _not_ associated with any specific platform. If you want asynchrony, but not necessarily concurrency, then use Scheduler.Asynchronous. If you're running in ASP.NET, then internally it will use the current SynchronizationContext to post notifications. If you're running in a Windows service, then it will just use CurrentThreadScheduler (or perhaps fallback to concurrency - I haven't thought it through fully yet).
Instead, Asynchornous should point to the current DefaultScheduler scheduler or something that uses Task/ThreadPool
No, that's exactly what Scheduler.Concurrent is intended for.
For windows apps, it should be named "Scheduler.WindowsUI", [snip]
This is much less useful because portable libraries don't care. In a portable library I want to write Scheduler.Asynchronous. In reality, everyone is using their own facades and then in the primary platform, such as a WPF app, replacing it with DispatcherScheduler. Scheduler.Asynchronous would simply be a standardization of this common pattern.
@RxDave you are correct, since they are general purpose, we want to keep their uses as general as possible. Most executions should happen immediately unless time or threading is introduced.
The simple rules at least for JS is:
@mattpodwysocki thanks, do you agree that my proposed scheduler abstractions for .NET make sense in general? I'd like to see them included in a future release.
The idea stems from a question of mine that Bart answered a couple of years ago.
@RxDave, your explanation makes sense. I guess I was just relating "asynchronous" to TPL which is by default concurrent. It doesn't need to be.
But still, the naming is confusing. If we have "asynchronous concurrent" and "asynchornous non-concurrent", why one of them is just called "asynchronous" then? It doesn't imply one but also doesn't imply the other.
I get it is just naming a convention, but for some people like me it means guidelines like "never use Scheduler.Asynchronous anywhere because it is not what it looks like, it will decrease performance by waiting for a specific thread even though this is not required by all asynchronous code, instead create your own facade and choose that only if you need this behavior". This would defeat the purpose of the proposal.
My point here is that "Asynchronous" is a very generic term, but is defaulting to a very specific use-case. The proposal itself is completely valid, but the names should be more appropriate.
Could be "AsynchronousConcurrent" and "AsynchronousNonConcurrent" or just "AsyncConcurrent" and "AsyncNonConcurrent", or something else, but certainly not just "Asyncrhonous". This name would be misleading.
If we have "asynchronous concurrent" and "asynchornous non-concurrent", why one of them is just called "asynchronous" then?
The "Asynchronous" scheduler doesn't necessarily mean "non-concurrent". In my original proposal a platform-specific async scheduler is used if available. In addition, for Rx's long-running optimization, NewThreadScheduler is used.
"never use Scheduler.Asynchronous anywhere because it is not what it looks like, it will decrease performance [snip]"
I think this is actually a common misconception and really bad advice. In fact, passing appropriate schedulers to query operators so they schedule notifications directly into thread-affine contexts often performs better and consumes less resources. You should actually want to avoid concurrency unless absolutely necessary.
Scheduler.Asynchronous would avoid unnecessary use of pooled threads. For example, a common misuse of concurrency is to apply Throttle followed by ObserveOn, but that just means that a pooled thread is used to send a notification into a queue, which is then dequeued on the UI thread later. A more efficient approach is to simply pass DispatcherScheduler directly to Throttle, thus avoiding use of a pooled thread and potentially an additional queue (ObserveOn may introduce a queue and the UI thread already has its own queue).
My proposal to have Scheduler.Asynchronous would allow portable libraries to essentially use DispatcherScheduler in operators such as Throttle. (Perhaps this particular example is not appropriate for general-purpose portable libraries, but it may be appropriate for "business layers" which I tend to write as portable libraries nowadays.)
I believe the Rx Design Guidelines has similar recommendations on this as well, but I don't have time at the moment to look for it.
"Asynchronous" is a very generic term, but is defaulting to a very specific use-case [snip]
It's not a specific use case. It actually covers a lot of ground. Its use would essentially mean, "I do not want to schedule notifications synchronously if it can be avoided, but I don't necessarily want notifications to be scheduled concurrently if it may actually hurt performance".
In fact, my actual recommendation was going to be to always use Scheduler.Asynchronous for everything, unless:
ObserveOn with a platform-specific scheduler.EventLoopScheduler.Off topic:
I just though of a better solution than my old comment about adding a new interface to optimize for EventLoopScheduler. If EventLoopScheduler were to simply register its own SynchronizationContext on its background thread, then perhaps any downstream usage of Scheduler.Asynchronous could discover it, thus propagating the usage of EventLoopScheduler without having to explicitly pass it along to infrastructure components within portable libraries.
To my previous point, one of the primary benefits of Scheduler.Asynchronous and Scheduler.Concurrent is that they free us from having to care about internal Rx optimizations when we're writing queries. Their usage is entirely semantic. Often Rx knows best, which is why we should generally use Scheduler.Asynchronous everywhere (and perhaps even Rx operators such as Throttle should have been designed in this manner, rather than introducing concurrency by default). We can always opt-in to concurrency when it's desired.
Quantitatively, I wonder on average how many queries actually do benefit from having inadvertent concurrency. I suspect that most in fact don't. It's great that Rx remains free-threaded, but in most cases when a query is created on the UI thread you want it to run entirely on the UI thread, even if you're using operators like Throttle. This would avoid most uses of ObserveOn. And as previously stated, when concurrency is actually beneficial it wouldn't hurt to be explicit about it:
keys.Throttle(TimeSpan.FromSeconds(1), Scheduler.Concurrent)
.Select(DoSomethingCostly)
.ObserveOnDispatcher();
Just to be clear, even with my previous proposal Rx would remain free-threaded (thankfully). As a reminder, Scheduler.Asynchronous does not mean "non-concurrent". When a query is formed on a background thread, it defaults to CurrentThreadScheduler, thus notifications remain on the same thread (unless Rx introduces a long-running optimization, in which case it will fallback to NewThreadScheduler, but again that's the point...)
I think this is actually a common misconception and really bad advice. In fact, passing appropriate schedulers to query operators so they schedule notifications directly into thread-affine contexts often performs better and consumes less resources. You should actually want to avoid concurrency unless absolutely necessary. ... Scheduler.Asynchronous would avoid unnecessary use of pooled threads. For example, a common misuse of concurrency is to apply Throttle followed by ObserveOn,
If Rx is defined as a library to handle UI queries in WPF apps, this is perfect. But if Rx is a general purpose library to write composable event based code, then it is not.
You are right that using appropriate schedulers improve performance, but wrong in that you can choose it just by knowing if you are running in asp.net, win forms or service. Even in a WPF or ASP.NET app you may have lots of code that are not long running, but don't require thread affinity.
Note that I think the solution your are proposing is fine, I just the naming you choose that is problematic. It makes sense to you for UI code, but not as a general purpose solution. If you give people "Scheduler.Asynchronous", it will be just like renaming "Scheduler.Default" and people will start using that without considering the impact. Developers should know exactly what they are choosing.
If I happen to be using a library that uses Scheduler.Asynchronous in my service, and I'm scheduling 100's tasks per second, your solution is terrible. I may have 64 processors but I'm waiting for that specific thread to be free. You may say "well, but that is a specific case". But the fact is that it is just another case, different from yours. And a general purpose library shouldn't pick sides, it should be neutral. This is what I'm proposing.
If there is going to be a change, just name things accordingly. If it can be concurrent or non concurrent, call it "UIOptimisticScheduler" or anything else, just not "Asynchronous".
That's my 2 cents. I think this proposal is great specifically to call code from UI and ASP.NET without specifying a scheduler, if that code is related to UI processing. Other than that people will keep creating their own facades anyway.
@nvivo I think you're misunderstanding my position. To be clear, I'm not stating that Rx can choose appropriate schedulers in all scenarios. I'm simply stating the following:
Throttle, introduce concurrency by default, though they'd function perfectly as expected if they relaxed a bit and simply defaulted to an "asynchronous" scheduler instead. This is not strictly part of my proposal, I've just introduced in more depth within our discussion, but I do believe that it's sensible. Your last response seems to be disagreeing with this point, but I think your reasons are flawed, which I'll explain below. I'm still not 100% convinced that this is the correct approach, but I don't feel that I've seen any convincing arguments against it yet.Scheduler.Asynchronous scheduler chooses the UI scheduler (barring Rx's ISchedulerLongRunning optimization; see # 1). I suspect that this is almost always going to be the most desirable approach, for performance reasons. However, as you've pointed out in your last response, when constructing a query on a background or pooled thread, the UI scheduler is generally incorrect. In fact, I've considered this already and that's part of what I've been trying to convey to you. Scheduler.Asynchronous can also act concurrently and take advantage of Rx's free-threaded nature. In my proposal, I specified that it would fallback to CurrentThreadScheduler when a UI isn't available. Consider the implications: When passed to a concurrency-introducing operator such as Throttle, it will cause it to create a threading Timer that raises events on pooled threads (within the Rx scheduling infrastructure), thus notifications will be on pooled threads. This is concurrency. In other words, in a server environment Scheduler.Asynchronous means "concurrent" for concurrency-introducing (timer-related) operators and "synchronous" for non-timer-related operators. This makes the best use of resources in that environment. The alternative currently in use is to force concurrency everywhere.You are right that using appropriate schedulers improve performance, but wrong in that you can choose it just by knowing if you are running in asp.net, win forms or service.
Essentially, I don't agree that your concerns are valid. I'm not proposing that you can't choose, I'm proposing that your choice of "Asynchronous" meets most needs in general, and therefore it's also perhaps a sensible default scheduler rather than the current behavior of forcefully introducing concurrency. I don't think that recommending concurrency as a default behavior in general is good advice, regardless of whether you're running in a GUI environment or in a high-performance service. The idea is to minimize concurrency and get the best performance possible, not to let concurrency run wild like the TPL. That's also why Rx is free-threaded, I suspect; it's an attempt to make the best use of system resources.
If I happen to be using a library that uses Scheduler.Asynchronous in my service, and I'm scheduling 100's tasks per second, your solution is terrible. I may have 64 processors but I'm waiting for that specific thread to be free.
Which thread do you mean? As I've pointed about above, your service will continue to be free-threaded. Pooled threads will be used if your operators are notifying on pooled threads.
However, if you're creating a query on the UI thread that's pushing through 100's of notifications per second via a concurrency-introducing operator, and that operator were to default to Scheduler.Asynchronous instead of Rx's current behavior, and if the performance was acceptable, then I'd argue that everything is perfect: It's using the least resources, has the least overhead, and it's performing acceptably. If on the other hand performance is unacceptable, and introducing concurrency would be beneficial, then my argument is that it's better to be explicit by passing in Scheduler.Concurrent, as shown in the code snippet in my previous reply.
If you give people "Scheduler.Asynchronous", it will be just like renaming "Scheduler.Default" and people will start using that without considering the impact.
No, it's not like that at all. Scheduler.Default introduces concurrency. Scheduler.Asynchronous states the following intention: "I prefer asynchrony over synchrony or concurrency."
To put it another way:
"If I'm on the UI thread, then I probably want to remain on the UI thread. If an operator needs to introduce asynchrony, then let it post back to the UI thread.
If I'm not on the UI thread, then remain on whatever thread I'm on (free-threaded). If an operator needs to introduce concurrency, then let it post back to whatever thread it deems appropriate; e.g., let it use a pooled thread or let it use a new thread - I don't care."
"Just don't block."
Hopefully that's clear up my points on this. If not, then maybe you can provide a concrete example of where you think my analysis is explicitly flawed.
Thanks,
Dave
@RxDave, ok, this is getting long and maybe we are discussing two different things.
Let me put this very clearly: I think your logic is correct, I don't think your analysis of the problem is flawed. But I think that providing a public "Scheduler.Asyncrhonous" with this logic is the wrong way to go.
So, to be clear, about your statements:
- Rx has optimizations that ignore the schedulers that you choose
- Rx has to choose a sensible default scheduler for each operator.
- I suspect that [the proposed logic for Scheduler.Asyncrhonous] is almost always going to be the most desirable approach
Agreed. If you call .Throttle() without any scheduler, your proposal is great and solves a lot of problems. I think this should be definitely included in the library. Now, to the issue:
it's not like that at all. Scheduler.Default introduces concurrency. Scheduler.Asynchronous states the following intention: "I prefer asynchrony over synchrony or concurrency."
I'm talking about semantics, not functionality. Currently, we have "Scheduler.Default" that may be concurrent, but what matters is that it is called "Default", so people will use it by default in most cases if they don't care.
Your proposal (correct me if I'm wrong) is to remove the Scheduler.Default and other references, and simply provide Scheduler.Synchronous, Scheduler.Asyncrhonous and Scheduler.Concurrent instead.
I like this idea of simplifying. If we need specific scheduler, we can just instantiate them, so less choices for common cases is good. The problem is the naming:
You say "Concurrent already exists, so it is clear Asyncrhonous is the other thing", I say: "just chose different names because this is confusing and misleading".
Now, I think one problem in this discussion lies in this comment:
The idea is to minimize concurrency and get the best performance possible, not to let concurrency run wild like the TPL
See, this is a matter of preference. I think it is valid for some types of code, but I don't see this as a rule of thumb. I for one like everything concurrent, and I prefer to always think concurrently and have exceptions where I have synchronicity. I think .NET and Rx makes it quite easy to sync when needed.
Having said that, I'm not proposing making everything concurrent. I'm proposing making the Scheduler static references clear about what they will return. So, instead of:
Make it general and no preference for any sides:
Now, as for "Asyncrhonous may return a concurrent scheduler in servers". I think the issue of selecting the default scheduler for extension is completely unrelated.
You just need a way to get this scheduler. You could call it:
As for this last one, you see, my problem is not with your logic, is just naming. I wouldn't mind it putting this logic into the "Default" property. It makes sense as a default, it makes some choices based on the environment and runtime and selects one that seems most appropriate. Looks like a "default" to me.
My problem is calling it the "Asynchronous", it implies "this is the preferred way to run asynchronous code everywhere", and it is not.
Offtopic
Just getting this off my head. It seems that:
Rx could instead provide a way to set this facade globally, similar to how ASP.NET MVC has some global configuration classes to set things up on startup.
If this façade proposal is implemented, it could have the properties as read-write or provide an API to set the values globally like "Scheduler.SetFacade(ISchedulerFacade), so we can really use this facade anywhere.
Now for that to work, schedulers need to merge IScheduler, ISchedulerLongRunning and ISchedulerPeriodic into a single interface, so we can replace the schedulers with any of the others, including TestScheduler. Currently these interfaces are not implemented everywhere, so it is hard to replace DefaultScheduler with TestScheduler.
I don't think your analysis of the problem is flawed. But I think that providing a public "Scheduler.Asyncrhonous" with this logic is the wrong way to go.
But that's one of the primary points of my logic :)
Agreed. If you call .Throttle() without any scheduler, your proposal is great and solves a lot of problems. I think this should be definitely included in the library.
Ok great, I'm following you so far...
Now, to the issue: [snip] I'm talking about semantics, not functionality.
Yes, I know, so I was defending my position about the term "Asynchronous" in the context of my understanding of the problem. But please continue... :-)
Currently, we have "Scheduler.Default" that may be concurrent, but what matters is that it is called "Default", [snip] Your proposal (correct me if I'm wrong) is to remove the Scheduler.Default and other references [snip]
Technically, I've never even used Scheduler.Default in my own code and I'm totally perplexed when I see it being used in other people's code. It makes no sense to me. "Default" means what? It's got no semantics at all, unless you know that means "concurrent", in which case Scheduler.Concurrent seems better, does it not?
So yes, I think it should go away. I don't recommend anybody use the Default scheduler - ever.
and simply provide Scheduler.Synchronous, Scheduler.Asyncrhonous and Scheduler.Concurrent instead.
Not exactly. Read the list again and you'll see that I've proposed to actually keep several of the existing schedulers because they are useful outside of these 3 generalized schedulers. Note that the platform-specific concurrent schedulers are _not_ included in my list, but the platform-specific UI schedulers are in fact included.
I like this idea of simplifying. If we need specific scheduler, we can just instantiate them, so less choices for common cases is good.
We're still in agreement then.
The problem is the naming:
And this is where we disagree :-)
1."Synchronous" means one thing
Yep, synchronous. It's synonymous with Scheduler.Immediate, though I'm not opposed to keeping "Immediate" instead.
2."Concurrent" means one thing
Yep, concurrent. :-)
3."Asynchronous" may mean 2 things: concurrent or not-concurrent
No, you're missing the point. It doesn't mean "concurrent or not-concurrent", it means "asynchronous", as in, "don't block me and use the most efficient scheduling for the job".
Keep in mind that the whole point of this proposal is to hide Rx's optimizations and to make it easier to add new optimizations in the future. This will matter again in a moment..
You say "Concurrent already exists, so it is clear Asyncrhonous is the other thing", I say: "just chose different names because this is confusing and misleading".
No, that's not what I'm saying. I'm saying that they intuitively have different meanings, just like "async/await" in C# 5 doesn't necessarily mean "concurrent/await". But if you feel that "async" isn't clear enough, then perhaps it's worth discussing synonyms. Here are few ideas:
Now, I think one problem in this discussion lies in this comment:
The idea is to minimize concurrency and get the best performance possible, not to let concurrency run wild like the TPL
See, this is a matter of preference. I think it is valid for some types of code, but I don't see this as a rule of thumb. I for one like everything concurrent [snip]
I'm sure that you really don't. You wouldn't want Select projecting notifications on another thread, right? Because it's a waste of system resources. Or would you prefer if every operator in Rx had overloads that accepted IScheduler parameters?
I don't think it's a matter of preference. I think there is an objective "best possible performance" in all cases. The cost of marshaling to different threads has to be weighed against the costs of the computation, environmental factors, etc. Now I'm not saying that it's necessarily deterministic, but there is certainly a "better" decision and a "worse" decision that can be made. Choosing concurrency over free-threadedness is not the best approach, IMHO. Apparently the Rx team agreed because Rx is free-threaded, unlike the TPL (though you can opt-in to running continuations synchronously.) Furthermore, async/await took the free-threaded approach (barring use of SynchronizationContext by default, which also lends some support to my proposal of defaulting all operators to Scheduler.Asynchronous).
Having said that, I'm not proposing making everything concurrent. I'm proposing making the Scheduler static references clear about what they will return. [snip]
We're still in agreement then...
Make it general and no preference for any sides:
- Scheduler.Syncrhonous
- Scheduler.AsynchronousConcurrent
- Scheduler.AsyncrhonousNonConcurrent
No, because what about optimizations? What if I don't care about concurrency, I just care about asynchrony? By specifying "async" it's up to Rx to choose. I think that's the major point you seem to be ignoring.
For example, Observable.Range is technically asynchronous by default (because of CurrentThreadScheduler) though since Subscribe blocks we can call it synchronous. We can make it concurrent by passing in Scheduler.Concurrent. We could also keep it asynchronous without any thread-pool overhead, if we're creating the query on the UI thread, by simply passing in Scheduler.Asynchronous. I'm proposing this should be the default, and you seemed to agree. But now consider a potential optimization: Hypothetically, Range from 1-10 may give better performance to observers without introducing concurrency, yet greater than 10 may give better performance with concurrency. So, based on the _count_ argument it either checks for ISchedulerLongRunning or not. Scheduler.Asynchronous will return NewThreadScheduler, thus performance is improved generally across all uses (of course you could always pass in Scheduler.Concurrent to force it or something like DispatcherScheduler to prevent it).
This could not work with your proposed schedulers. Concurrent means concurrent, "not concurrent" means not concurrent. No room for optimizations.
Again, the point is that Rx does in fact do these kinds of optimizations now and I'd like to have a scheduler façade that both _simplifies scheduling and enables optimizations_. (Note that the optimization for Range indicated above was just a hypothetical example to point out what's potentially possible in the future, though I doubt the Rx team will do anything like that.)
Now, as for "Asyncrhonous may return a concurrent scheduler in servers". I think the issue of selecting the default scheduler for extension is completely unrelated.
I wasn't necessarily referring to default scheduling. An API could (and should) supply Scheduler.Asynchronous to various operators whenever it makes sense. It makes sense when you don't necessarily care about concurrency, you just care about "don't block, perform well". It could very well be concurrent. It's not necessarily a "default" concept. It just so happens that if it were the default in Rx, then you wouldn't have to pass it yourself. Instead, you could pass platform-specific schedulers when in fact concurrency was absolutely required or thread-affinity was absolutely required.
You just need a way to get this scheduler. You could call it: [snip]
As for this last one, you see, my problem is not with your logic, is just naming. I wouldn't mind it putting this logic into the "Default" property. [snip]
But you see, it doesn't have to be the default scheduler. Default scheduling could remain concurrent in various operators and my proposal (along with the naming of "asynchronous") still applies.
My problem is calling it the "Asynchronous", it implies "this is the preferred way to run asynchronous code everywhere", and it is not.
It is, by default. But it doesn't have to be anyway and the term "asynchronous" is still the most appropriate that I've seen thus far. So let's assume that it's a bad default value. Fine, then operators such as Throttle will continue to be concurrent by default. What makes you think that people are going to start explicitly passing Scheduler.Asynchronous around everywhere? They don't do that with Scheduler.Default now. (I hope not, because that would seem to be an awful way to use Rx.)
We will have to agree to disagree then. See...
It doesn't mean "concurrent or not-concurrent", it means "asynchronous", as in, "don't block me and use the most efficient scheduling for the job".
...
what about optimizations? What if I don't care about concurrency, I just care about asynchrony? I think that's the major point you seem to be ignoring.
I'm not ignoring anything. I'm saying that there are lots of cases where you can't say "I don't care about concurrency, just make it asynchronous". In many cases, those things are synonymous. Because of that, you can't possibly know what is "the most efficient scheduling for the job" unless you look at the code and check what the person is doing.
And specifically if you are developing a library, you can't say "use anything you want". Code scheduled to run on the calling thread has implications, and if that is not explicitly needed, concurrency is the safest bet, because you have no idea how someone will be calling your code.
UI code is usually in an event loop, it is easy to say "I don't care". But a lot of code, specifically background services may never actually free a calling thread or may take a really long time to do so, making it possible that your scheduled code never runs. You can always sync back from code running on another thread, but you can't do anything about a code that will never run, this is why the safest bet is concurrency.
I have no more ways to say I'm _strongly against_ having a public property called "Scheduler.Asyncrhonous" with this specific behavior. I believe this would cause unecessary confusion for a lot of people.
I do believe your logic should be provided by SOME property, that would be give "the best asynchronous scheduler for scenarios where the code would better run on the same thread, but could run somewhere else and is also running in an event loop", with an appropriate name.
I'm starting to feel that is is not possible to define a simpler façade to simplify choice for developers. The reality is that people do different things. I have coded zero lines of WPF/WinForms code in the last 5 years at least and have no intention of starting anytime soon, and this is true for most .NET developers I know. Your main worry is just not an issue to most people around here. So, we will never agree on what are the most common cases.
If the Rx developers go with this solution, that's fine for me though. I just think it would be confusing for a lot of people.
I'm saying that there are lots of cases where you can't say "I don't care about concurrency, just make it asynchronous". [snip]
you can't possibly know what is "the most efficient scheduling for the job" unless you look at the code and check what the person is doing. [snip]
And specifically if you are developing a library, you can't say "use anything you want".
But a lot of code, specifically background services may never actually free a calling thread or may take a really long time to do so, making it possible that your scheduled code never runs. [snip]
Yes, I basically agree with all of these statements. But why can't you just use Scheduler.Concurrent then? And what makes you think that code running on a pooled thread may take such a long time that code scheduled by Scheduler.Asynchonous via the CurrentThreadScheduler would never run? That would be a really poor design to begin with, because actions on pooled threads should be fast, but if you must have concurrency then choose Scheduler.Concurrent.
I have no more ways to say I'm strongly against having a public property called "Scheduler.Asyncrhonous" with this specific behavior. I believe this would cause unecessary confusion for a lot of people.
I don't understand why you're strongly against having a scheduler that encapsulates what Rx is generally all about (namely, asynchrony) and leaves room for optimizations. But even if you don't like it, then why not just use a different scheduler? I'm not proposing that it should be the _only_ scheduler!
And I don't understand why you think it will be confusing to people, especially considering the current state of Rx scheduling and its most common uses (I suspect). I mean, you're not confused by the Default scheduler? You're not confused by passing in ThreadPoolScheduler yet you may receive notifications on non-pooled threads anyway? You're not confused by Task conversions introducing concurrency when your query is defined on the UI thread? You're not confused by Throttle and other operators introducing concurrency when your query is defined on the UI thread?
The whole point of this proposal is to simplify scheduling to the most common scenarios by defining a façade that also respects optimizations. One of those schedulers encapsulates the primitive notion of "asynchrony". You're not forced to use it. You can still pass Scheduler.Concurrent around everywhere if you want to. And if people are confused about the difference between "async" and "concurrent", then just point them to the async/await feature, noting that it wasn't called "concurrent/await" for at least one very important reason.
The reality is that people do different things. I have coded zero lines of WPF/WinForms code in the last 5 years at least and have no intention of starting anytime soon, and this is true for most .NET developers I know. [snip]
You're wrong that I'm simply trying to propose something focused only on UI development. I know you've hinted at it a few times but I've ignored it because I thought it was going to be implicitly addressed in my replies, but apparently not, so I'll address it directly.
You've mentioned ASP.NET already, which also has its own SynchronizationContext, and I've used it with Rx and remote services before, so I know that Scheduler.Asynchronous would work great as an alternative. Running in Azure, or any service environment, would not only make good use of Scheduler.Asynchronous but it would be far more common than using Scheduler.Concurrent. The former still introduces concurrency in all of the places that it matters, such as operators that require timers and operators that are long-running, but in any other place they would simply work synchronously, which is _exactly how Rx works now_ unless you pass in concurrency-introducing schedulers everywhere, barring the default scheduling of certain operators, which even in my proposal you're still free to do. That's what Scheduler.Concurrent is for.
Your main worry is just not an issue to most people around here. So, we will never agree on what are the most common cases.
What's my main worry? Abstracting asynchronous scheduling in Rx, with respect to optimizations. It's certainly not "UI development", if that's what you mean.
If the Rx developers go with this solution, that's fine for me though. I just think it would be confusing for a lot of people.
So just to be clear, you're fine with everything in my proposal except for the following 2 things.
Scheduler.Asynchronous: You're not opposed to its concept, just it's name. You're concerned that service devs will accidentally use it everywhere when you feel that they should be using Scheduler.Concurrent everywhere, and they'll be confused as to the differences between the schedulers; however, you're not opposed to making it the default scheduler, presumably because then it's out of view. (I doubt the Rx team would do that anyway - it's quite a breaking change, but I hope they strongly consider it).Scheduler.Asynchronous and Scheduler.Concurrent in a reasonable way. Essentially, you just don't like Rx optimizations.Is this synopsis of your positions correct?
You're concerned that service devs will accidentally use it everywhere [...] they'll be confused as to the differences between the schedulers
No, not at all. People passing specific schedulers around need to know what they are doing. I'm more concerned about the "WTF factor" when looking at the public API.
I'm looking at the future documentation:
So far you didn't provide any argument to remove this "WTF?" from my mind.
Yes, it is just naming, but I think it is important. If it is not, than just go for it.
An "WTF?" baloon will always pop up above my head everytime I look at this though. =)
You're definition is _still_ needlessly complicated and you're _still_ confusing _concurrent_ for _asynchronous_ :-)
Future documentation:
_Scheduler.Asynchronous:_ Returns a scheduler that does its best to ensure that Subscribe doesn't block and allows Rx to optimize where the scheduler executes based on an operator's semantics, the current platform and the current context in which the query is defined. This should be the primary scheduler you choose in most scenarios, client or server, device or desktop, "layer", or "purpose".
If you want to force concurrency (with optimizations), then choose _Scheduler.Concurrent_ instead.
If you want to force thread-affine asynchrony, then choose a platform-specific UI scheduler, _EventLoopScheduler_ or _CurrentThreadScheduler_ instead.
Keep in mind that Rx is already doing some of this. I'm just proposing to make it explicit.
Well, sounds like you have a perfect proposal and no comment was needed after all. Good job.
Sorry if I've offended you in any way. We're just having a friendly discussion. I simply don't accept any of your arguments against the word "Asynchronous". To me it's clear and I feel that I've explained it thoroughly enough. If other devs will agree with you, then of course I'd be happy to change my mind.
But frankly it doesn't matter what I think. As you've noted, it's been 2 years and nothing came of it. I'm not holding my breath.
I simply don't accept any of your arguments against the word "Asynchronous"
Exactly, it is not like there is none, is just that you won't accept any of them. No offense taken.
As you said _this should be the primary scheduler you choose in most scenarios, client or server, device or desktop, "layer", or "purpose"_. Looks like you have it all covered and that is a perfect solution.
I just won't choose them because it won't make sense to put this restriction in most of the code I write for windows or asp.net apps.
it won't make sense to put this restriction in most of the code I write
Can you elaborate on "restriction"?
I asked for a concrete example before. I'd still like one if you've got one.
I said this already, you just won't listen.
I don't think having to wait for the calling thread is something most code requires, even in windows or asp.net apps. Most of the code I write in RX is not related to UI or asp.net context, so it really doesn't care which thread it runs in, and I'd better run it as soon as possible then wait for a specific thread.
It doesn't matter if Rx does this today in "Default", the problem is that you want to make this explicit by saying "most asyncrhonous code should run like this on any platform for any case". This is just stupid.
But really, I'm tired of this discussion. Let's just agree to disagree.
I'm listening, I just don't agree with your comments because they're continuously based on a misunderstanding.
I don't think having to wait for the calling thread is something most code requires [snip]
I've never stated anything about being forced to wait for any calling thread in a server application. Where are you getting that from? I've described what "async" means several times already. I've described that in the contexts of your favorite kind of app (server, no UI, no sync context) you'll have absolutely no problem using Scheduler.Asynchronous rather than Scheduler.Concurrent. In a server context, it's equivalent to Scheduler.Concurrent for any operator that introduces concurrency (timers, long-running, etc.). And so what if you don't like it for reasons x, y or z, then just use Scheduler.Concurrent everywhere - you've got a choice.
So where's the problem? You just don't like the word "asynchronous", though you've stated that you agree with my logic. My logic is right there ^^
And if I agreed that something like Scheduler.NonBlocking was a fine name, but the behavior was identical, then you'd have absolutely nothing more to say? We'd be in 100% agreement? (I really doubt it.)
It doesn't matter if Rx does this today in "Default". [snip]
Rx doesn't do my proposal. That's why I'm proposing it. What Rx _does_ do is ignore the concurrency-introducing scheduler that we specify in some cases (concrete example) and grab the current sync context in other cases (FromEventPattern description). This "default" optimized behavior led me to propose to hide the former behind the Scheduler.Concurrent façade, which led me to the realization that I could hide the latter behind the Scheduler.Asynchronous façade. It turns out that since "asynchrony" clearly covers "concurrency", and given our intuitions when forming queries in thread-affine environments, it seems that the latter would serve as a great default behavior for all operators (so I claim, though I don't expect the Rx team to necessarily go that far, as I've already admitted. Regardless, I'd use this scheduler explicitly in most cases anyway rather than doing strange things like using ObserveOn or having to create my own façade for portable libraries.)
"most asyncrhonous code should run like this on any platform for any case". This is just stupid.
I don't agree that everything should be concurrent, if that's what you mean. _Especially_ not by default.
You never answered one of my questions: What is your opinion about the Select operator? Should it project notifications concurrently by default and have an overload with an IScheduler parameter? Should all operators?
Well, thanks for your time discussing this. For the sake of moving on, I'll agree to disagree. (I wouldn't want any more of my ideas being called stupid.)
@RxDave,
The fact is that so far, you didn't provide any reason for why this behavior should be the "standard asyncrhonous behavior for all the cases".
Your words:
This should be the primary scheduler you choose in most scenarios, client or server, device or desktop, "layer", or "purpose"
If that was the case, Rx would have implemented this by default a long time ago. It's like that giant pot of gold was right there in the open all along and nobody saw it. I just don't think that is the case.
It is clear to me this behavior is good in some cases, but I don't think it is most or all of them. So, providing a public API implying that to me is a mistake.
I'll probably be making a pull request in the future.
The fact is that so far, you didn't provide any reason for why this behavior should be the "standard asyncrhonous behavior for all the cases".
Right, I'll try again in more explicit terms:
Rx is an asynchronous model, thus it supports synchrony, concurrency and cooperative asynchrony. In general, we expect that Rx does not block and does not introduce concurrency. We also expect Rx to be free-threaded in nature.
In fact, there are only 2 scenarios where Rx introduces concurrency:
Note that recommendations provided by the Rx Design Guidelines seem to make my case for resolving bug #21 (Bart also agreed with me over email that it needs to be fixed though I haven't worked out the finer details yet) and for having asynchronous scheduling by default for all operators that apply to # 1 above. The guidelines state that (paraphrased) ObserveOn is often the wrong approach because it fails to avoid unnecessary thread context switches and introduces an unnecessary queue (though in fact there are optimizations that avoid the queue when a SynchronizationContextScheduler is passed or a DispatcherScheduler-specific overload is chosen.) I could go on, but I don't have to appeal to this document because I can make another case that's clearly UI-agnostic, to hopefully satisfy you.
I've argued in this discussion that # 1 is perhaps wrong and that the default scheduler for all operators should be Scheduler.Asynchronous, which would also fix bug #21. (Though this scheduler would be the primary scheduler most people should use moving forward even if the Rx team decides not to use it as the default for all of the currently concurrency-introducing operators, which seems likely to me.)
I've described the reasons for this before, but here is a consolidated list:
CurrentThreadScheduler, which is asynchronous. Introducing concurrency doesn't change this fact.FromEvent*) capture the current SynchronizationContext and use it to introduce asynchrony (or even concurrency, depending upon the context in which an event is raised).Scheduler.Concurrent, which is behaviorally equivalent to Scheduler.Default. Furthermore, Scheduler.Synchronous is behaviorally equivalent to Scheduler.Immediate, thus I've imagined Scheduler.Asynchronous to fill the gap.Scheduler.Asynchronous hides, yet allows for optimizations (the primary purpose of this proposal). It's the behavior of Scheduler.Concurrent without forcing the "concurrent" part.And here's why:
In fact, my case is similar to the case for async/await, which is used in both UI and server environments without any question as to its usefulness and applicability. It fully supports concurrency when a function introduces it and single-threaded asynchrony when a function doesn't. It captures the current SynchronizationContext by default, though it also provides _ConfigureAwait(false)_ to opt-out when necessary (all we'd have to do is explicitly pass in CurrentThreadScheduler or Scheduler.Concurrent instead, depending upon our needs. Generally it would be the latter, as I've suggested several times throughout our discussion).
I'm not proposing that a purely "async" scheduler is necessarily UI- or single-threading-dependent, as you can hopefully see now from the analogy above.
It's the correct default behavior because:
CurrentThreadScheduler)SynchronizationContext is available, asynchrony is the most sensible default behavior (revealed by experience), rather than blocking or concurrency.ConfigureAwait in our portable libraries. Rx is free-threaded, so operators like Select will just work without introducing any overhead. Operators or custom code that must introduce concurrency will work, without introducing any overhead.It's probably not even an exhaustive list (not an excuse, I'm just really busy at the moment and I have to go...)
Now, you haven't provided any sufficient evidence for your case that asynchrony is _not_ the best default. Should I ask a third time for a concrete example? :-)
I don't know exactly why we keep this bikeshedding. I agree with all your reasons to include this functionality, I just don't agree with the naming and the place this is being put.
There is a huge difference between doing something by default and explicitly telling everyone "this should be your default for most situations". Creating something called Scheduler.Asynchronous does the later, and I don't think this is appropriate thing for Rx to decide that.
If there is a default, you don't need to tell people they should also choose it by default when they are not using the default!
So, trying (again) to be clear:
But:
If that property is called anything else and used extensively by Rx and your code, it will still achieve all your goals in the same way, won't it? You ask me why I care about the name of the thing, why do you?
Now, you haven't provided any sufficient evidence for your case that asynchrony is not the best default.
I didn't say that, it is the naming you choose for this public API that I'm opposing.
Either we are talking apples and oranges or you just want me to say I agree with you. That won't happen, you are not providing any argument to prove me wrong either.
Now I really doubt 99% of developers care about the problem itself, even more so about the solution someone provides. If the Rx team accepts your PR, that's fine.
I'm tired of this discussion. It is my opinion, we don't have to agree on that.
Well, I feel I've already sufficiently addressed (with evidence) your points about my choice of naming, Rx's current async behavior (default and operator-specific), what's intuitive and appropriate behavior for Rx in general (client and server), how intuition and actual behavior overlap, especially with regards to existing and potential performance optimizations, and how my proposal could potentially bring these ideas together in a simple and general way. You disagree. A PR is clearly the next step for me, though it's not going to convince you I'm sure, but frankly I don't have the time now anyway.
Essentially, you don't agree, I don't agree, or you don't understand and I don't understand, or some combination of both. Perhaps the conversation is too abstract. I suspect you'll say exactly the same thing to me, so I'm going to respectfully back out of the conversation now. Thanks, it was interesting.
FWIW Dave's proposed names and meanings make sense to me.
This is a really old issue and I'd say in the meantime, a lot of traditionality built up around the scheduler names as they are.