Can't figure out ControlScheduler
-
Sunday, December 20, 2009 4:54 PM
I created an empty WinForms app, added a Label ("label1") and hooked Load as follows:
private void Form1_Load(object sender, EventArgs e) { var scheduler = new ControlScheduler(label1); Observable .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)) .SubscribeOn(scheduler) .Subscribe((a) => { this.label1.Text = a.ToString(); }); }Somehow this is still throwing "Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on."
Is there a bug here or am I not understanding how this is supposed to work (I'm guessing the latter)?
All Replies
-
Sunday, December 20, 2009 6:28 PM
The problem is that you are using the scheduler to subscribe to the source, but not to receive notifications. Admittedly, this would be much more clear with some documentation ;). The code that you wrote will actually will do the following:When the form is loaded, subscribe to the observable sequence. The subscribe operation will post a message in the message loop associated with label1 to subscribe to the timer which will run in the ThreadPool. The timer sends messages to the ThreadPool which try to update the text of the label.So in summary, use SubscribeOn when the subscription process is thread-affine. Use ObserveOn when the notification process is thread-affine. So you could have written code like:private void Form1_Load(object sender, EventArgs e) { var scheduler = new ControlScheduler(label1); Observable .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)) .ObserveOn(scheduler) .Subscribe((a) => { this.label1.Text = a.ToString(); }); }This would post all messages from the Timer to the message loop associated with label1. But this is probably not good either even though it won't throw an exception. The reason is that the timer still runs on the ThreadPool, and it is simply posting messages to process on the UI thread. Instead, we want a timer that runs on the UI thread. To achieve this use the following code:private void Form1_Load(object sender, EventArgs e) { var scheduler = new ControlScheduler(label1); scheduler .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)) .Subscribe((a) => { this.label1.Text = a.ToString(); }); }Now, no posts are needed and all concurrency is created in the right place.- Proposed As Answer by Steffen ForkmannMVP Sunday, December 20, 2009 7:47 PM
- Marked As Answer by korggy Sunday, December 20, 2009 7:56 PM
-
Sunday, December 20, 2009 6:36 PMOwnerThe names "SubscribeOn" and "ObserveOn" are rather tricky. They indicate where the indicated operation happens, i.e. SubscribeOn cause the subscription to happen in some context. Hence here, instead of SubscribeOn, you should use ObserveOn because you want the observation a => { label1.Text = a.ToString(); } to run on the UI thread.private void Form1_Load(object sender, EventArgs e){var scheduler = new ControlScheduler(label1);Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)).ObserveOn(scheduler).Subscribe(a => { label1.Text = a.ToString(); });}or prevent it alltogetherprivate void Form1_Load(object sender, EventArgs e){var scheduler = new ControlScheduler(label1);Observable.Timer(scheduler, TimeSpan.Zero, TimeSpan.FromSeconds(1)).Subscribe(a => { label1.Text = a.ToString(); });}

