Search code examples
c#windows-8windows-phone-8task-parallel-libraryportable-class-library

How to get back to UI thread after asynchronous operation


I am developing two versions of the same app for both Windows Store and Windows Phone 8 using the MVVM pattern. Each app has its own View. Model and ViewModel are shared in a Portable Class Libraray. I'm doing asynchronous operations within the Model by using TPL Tasks. Due to the restrictions of the Portable Class Library, I cannot use the async and await keywords.

After a Task has finished I want to come back to the UI thread and update some properties (which will cause the ViewModel and View to update too).

This seems to me like a quite common situation so I'm a little confused why it turns out to be so hard.

I tried two different approaches:

One (does not work)

Save a reference to the scheduler before starting the operation

TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();

then pass it to the ContinueWith method.

myTask.ContinueWith(t => myTaskCompleted(t.Result), scheduler);

This seems to me lika a good solution but does not work. myTaskCompleted is still executed in a different thread.

Second

Now I tried to use

Dispatcher.RunAsync(CoreDispatcherPriority.Normal, handler);

Because I cannot use the Dispatcher from the PCL directly, I pass a reference to it (hidden in a wrapper) to almost every object in the model. (Like in this answer) This finally works but it's quite complicated and ugly.

So my questions are:

  1. Which is the recommended way to get back to the UI thread inside a Portable Class Libraray?
  2. What is my mistake in attempt One?

I know there already are lots of questions to this topic but unfortunately nothing really solved my problem.


Solution

  • TPL will use a thread from the thread pool, and the UI thread is the 'main thread' which isn't on the thread pool and never becomes available to run tasks upon. Using the ContinueWith function will grab another thread from the thread pool to execute your code upon. The core of the problem you are facing is in the fact that Windows Phone does not queue property changes and will make a direct attempt to update the view. Somewhere in your code you should have a Changed function which broadcasts property changes. I'll use mine:

    public void Changed(string Key) {
        // Check if the property changed has subscribers.
        if (PropertyChanged != null) {
            // Invoke the property changed.
            PropertyChanged(this, new PropertyChangedEventArgs(Key));
        }
    }
    

    This Changed function will work fine under a WPF application, because WPF will queue property changes and handles them on the next UI frame update. Since Windows Phone does not, we need to establish a pattern to change this behaviour at runtime. I made a property called Dispatcher, which I allow to be set at run-time. All my broadcasts have now been changed from Changed to Dispatcher.

    private Action<string> _Dispatcher;
    public Action<string> Dispatcher {
        get {
            if (_Dispatcher == null) {
                return Changed;
            }
            return _Dispatcher;
        }
        set {
            _Dispatcher = value;
        }
    }
    

    So now we can change the Dispatcher at run-time in our Windows Phone application. We need to write a function that postpones changes until the UI thread is active to broadcast the changes. I did this in an extension so it is a little easier to attach the UI thread safety on a ViewModel. The run-time changes will simply use Windows Phone Dispatcher to scheduele broadcasts on the UI thread. The implementation is as followed:

    public static void Attach(this ViewModelStore ViewModelStore, DependencyObject DependencyObject) {
        // Set the changed event dispatcher.
        ViewModelStore.Dispatcher = (Key) => {
            // Begin invoking of an action on the UI dispatcher.
            DependencyObject.Dispatcher.BeginInvoke(() => {
                // Raise the changed event.
                ViewModelStore.Changed(Key);
            });
        };
    }
    

    The ViewModelStore is the generic class I have been using for all my view models, so this function allows me to attach the thread-safe broadcasting mechanism to all of the view models. DependencyObject is a UI-component, such as a view. Now, all you really need to do is call attach on the view model.

    ProviderViewModel.Attach(this); // This is inside a Page.
    

    All the broadcasts are not delegated to the UI-thread and invoked the next frame the UI comes in and updates everything accordingly. You won't have to worry about thread safety like this, but you need to remember to Attach a new instance of a view model in your Windows Phone application. Let me know if there are further questions, and good luck!