WP7 & Caliburn.Micro: Coroutine threading
I was having some problems with Caliburn.Micro’s coroutines when working with a new Windows Phone 7 project. The execution of the coroutine stopped in the middle of its process, and where I should have seen a popup dialog, I didn’t see anything. The execution just stopped. Turns out, this was a threading issue. I didn’t realize that after the yield returnstatement, the execution continues from the thread where the previous task has completed.
For example, here’s a method hooked up to a button:
public IEnumerable<IResult> Run() { Debug.WriteLine("Current thread: {0}", Thread.CurrentThread.ManagedThreadId); yield return new MyService(); Debug.WriteLine("Current thread: {0}", Thread.CurrentThread.ManagedThreadId); yield return new MyService(); Debug.WriteLine("Current thread: {0}", Thread.CurrentThread.ManagedThreadId); }
And here’s the MyService-implementation:
public class MyService : IResult { public void Execute(ActionExecutionContext context) { ThreadPool.QueueUserWorkItem(x => RunLongRunningProcess()); } private void RunLongRunningProcess() { Thread.Sleep(1000); Completed(this, new ResultCompletionEventArgs()); } public event EventHandler<ResultCompletionEventArgs> Completed = delegate { }; }
MyService executes a long running operation so it starts a new thread. In real world it’s frequent that your IResult-class wraps a service which does some threading for you. Now, when the Run-method is executed, the output looks like this:
Current thread: 258736214
Current thread: 252444786Current thread: 255262834
Because we raise the Completed-event in a new thread every time, the Run-method will continue from that same thread. In my problematic case, I started the coroutine from a UI-thread and expected to stay on it until the end. But somewhere in the middle I started some task with a new thread, causing my next execution of ShowDialog to fail because I wasn’t in the UI-thread anymore.
You can easily fix this situation by some thread marshaling. Instead of just raising the Completed-event, raise it in the UI-thread:
private void RunLongRunningProcess() { Thread.Sleep(1000); Caliburn.Micro.Execute.OnUIThread( () => Completed(this, new ResultCompletionEventArgs())); }
With this small change, our Run-method stays in the UI-thread until end. Here’s the output:
Current thread: 235012186
Current thread: 235012186Current thread: 235012186