I am getting
Invalid cross-thread access.
When using RX Throttle
Here is my code:
yObs.SubscribeOnDispatcher()
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliseconds(33))
.SkipWhile(y => !_isDragging)
.Subscribe(y =>
{
// Exception when trying to access image
image.RenderTransform = new CompositeTransform() { TranslateY = -y };
_vm.UpdateContentDrag(y / image.ActualHeight * 100);
});
But if I omit throttle everything works.
As far as I understand Throttle uses thread pool so OnNext doesn't happen on UI thread. But SubscribeOnDispatcher should marshal it back to the UI thread. Shouldn't it?
Your understanding of SubscribeOnDispatcher is incorrect. First of all, let's distinguish between two *On operators:
- SubscribeOn* - Runs the (un)subscription logic on the specified scheduler. Rarely used, unless you play with Observable.Create etc.
- ObserveOn* - Runs the observer messages (OnNext, OnError, OnCompleted) on the specified scheduler. Mostly used for UI synchronization when running the "event handlers" passed to Subscribe.
In order for your sample to work, you also should stick the ObserveOn operator use further downstream in the query. Our recommendation is to do so right in front of the final Subscribe call. Within the query, concurrency can be introduced by operators such as Throttle (whose default scheduler is the thread pool). Only at the point you need synchronization guarantees, introduce a *On operator.
Paul's suggestion to parameterize the Throttle call is a good one too. In cases where you can control all the concurrency introduced, you may want to do so. However, there are many cases where you are handed an IObservable sequence that is ill-behaved with regards to synchronization requirements, requiring the use of *On operators.
Just change the line to:
.Throttle(TimeSpan.FromMilliseconds(33), DispatcherScheduler.Instance)
It's more efficient anyways (though 33ms is a really short timespan throttle, hitting up against the timer resolution)