Task UI synchronization

When dealing with asynchronous programming, the UI experience may achieve great improvement. The first look at any Windows Phone or Windows Store application will easily grant such feedback because of the obligation Microsoft made to SDKs for such platforms.

Dealing with an application that never waits for any external/internal resource or computation, and always remains responsive, is a great feature. The drawback is that Windows Forms and WPF controls are unable to easily update their user data using asynchronous threads (this limitation doesn't exist on ASP.NET).

Both frameworks, Windows Forms and WPF, implement their controls on a Single Thread Apartment (STA) with affinity. This means that all objects born on the starting thread will be available only throughout this thread. This affinity works like a firewall that prevents any other thread from accessing the resources behind it. So, although it is possible in asynchronous tasks/threads that do computations or that consume resource usage, when they are exited, any UI update must flow from the initial calling thread that created the UI controls, and maybe the same one that started the tasks.

This is a simple example that produces an exception in WPF:

//this is a no-MVVM WPF script
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
            //asynchronously set the Content of a Label on UI
            Label1.Content = "HI";
        });
    }
}

When the Content property is set, InvalidOperationException type is raised with this description: The calling thread cannot access this object because a different thread owns it. As said previously, before the preceding example, all UI controls live within a single thread in STA mode. This is why we have such behavior in WPF (and in Windows Forms too).

The solution is easy here because this is a script in WPF code-behind. It is enough to use a Dispatcher class, already exposed by any WPF control that works as a bridge to ask the UI thread to do something we want. The preceding task becomes this:

Task.Factory.StartNew(() =>
{
    Thread.Sleep(3000);

    var computedText = "HI";

    //asynchronously set the Content of a Label on UI
    Dispatcher.Invoke(() =>
        {
            //this code will execute on UI thread in synchronous way
            Label1.Content = computedText;
        });

});

When working in MVVM, a dispatcher is useless when a notification occurs. When any class that implements the INotifyPropertyChanged interface raises its PropertyChanged event, any control in binding with any property of this object, will read the underlying data again, refreshing the UI control state. This inverted behavior breaks the need for a direct cross-thread invocation with the dispatcher. The following is an example using the view model:

//a simple viewmodel
public sealed class SimpleViewModel : INotifyPropertyChanged
{
    //supporting INotifyPropertyChanged is mandatory for data changes notification to UI controls with data binding
    public event PropertyChangedEventHandler PropertyChanged;

    //a simple helper method for such notification
    private void Notify([CallerMemberName]string name = null)
    {
        if (PropertyChanged != null && name != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    public SimpleViewModel()
    {
        //the task is fired as view model is instantiated
        Task.Factory.StartNew(OnAnotherThread);
    }

    private string text;
    public string Text { get { return text; } set { text = value; Notify(); } } //this is how notify fires

    private void OnAnotherThread()
    {
        Thread.Sleep(3000);

        //asynchronously set a text value
        Text = "HI";
    }
}

To avoid coupling between a ViewModel and a View, we need supporting cross-threaded operations within a ViewModel with the ability to execute some UI update in the proper thread (the UI thread one), although we need using the Dispatcher from within the ViewModel itself, we may use it without having the View passing it to the ViewModel in a direct way. The ViewModel, can simply store the initial creation thread in it's constructor code, and later use such thread to ask for a dispatcher linked to this thread. This choice gives the ability to make cross-threaded operations against object living within the UI thread, without having the ViewModel to directly interact with View. The following is an example:

//a simple ViewModel
public sealed class CrossThreadViewModel : INotifyPropertyChanged
{
    //supporting INotifyPropertyChanged is mandatory for data changes notification to UI controls with data-binding
    public event PropertyChangedEventHandler PropertyChanged;
	
    //a simple helper method for such notification
    private void Notify([CallerMemberName]string name = null)
    {
        if (PropertyChanged != null && name != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    Thread creatingThread;
    public CrossThreadViewModel()
    {
        //in the constructor the caller thread is stored as field
        creatingThread = Thread.CurrentThread;

        Texts = new ObservableCollection<string>();
        Task.Factory.StartNew(OnAnotherThread);
    }

    //an observable collection is a self-notifying collection
    //that must be accessed by the creating thread
    private ObservableCollection<string> texts;
    public ObservableCollection<string> Texts { get { return texts; } set { texts = value; Notify(); } } //this is how notify fires

    private void OnAnotherThread()
    {
        Thread.Sleep(3000);

        //asynchronously create a text value
        var text = "HI";

        //add the value to the collection with a dispatcher
        //for the creating thread as stored
        Dispatcher.FromThread(creatingThread).Invoke(() =>
            {
                Texts.Add(text);
            });
    }
}

Another solution is available to access collections from multiple threads within a WPF application in .NET 4.5 or greater. We can request the WPF that is synchronizing an STA object, by using a lock and simply invoking the BindingOperations.EnableCollectionSynchronization method. Here the previous example is modified using the EnableCollectionSynchronization class:

static object lockFlag = new object(); //the collection accessing lock
Thread creatingThread;
public CrossThreadViewModel()
{
    //in the constructor the caller thread is stored as a field
    creatingThread = Thread.CurrentThread;

    Texts = new ObservableCollection<string>();
    BindingOperations.EnableCollectionSynchronization(Texts, lockFlag);
    Task.Factory.StartNew(OnAnotherThread);
}

//an observable collection is a self-notifying collection
//that must be accessed by creating thread
private ObservableCollection<string> texts;
public ObservableCollection<string> Texts { get { return texts; } set { texts = value; Notify(); } } //this is how notify fires

private void OnAnotherThread()
{
    Thread.Sleep(3000);

    //asynchronously create a text value
    var text = "HI";
    Texts.Add(text);//no more dispatcher needed
}

Although this solution will avoid the need of collection synchronization, this will not avoid all of the cross-thread issues, and sometimes we still need to use the dispatcher using the synchronous ViewModel creation thread, as shown in the previous example.

When working in Windows Forms, although the dispatcher is unavailable, a solution always exists for such cross-thread issues. It is enough to ask the control (or Windows Form) to do the cross-thread operation for us, using a delegate identical to the one from the dispatcher.

The following is an example of a wrongly-made cross-thread operation that will generate the same InvalidOperationException exception already seen in the WPF example:

public Form1()
{
    InitializeComponent();

    Task.Factory.StartNew(() =>
        {
            //this code will fail
            label1.Text = "Hi";
        });
}

The following code example shows the right way to avoid the InvalidOperationException class:

public Form1()
{
    InitializeComponent();

    Task.Factory.StartNew(() =>
        {
            //async elaboration
            var text = "Hi";

            //this asks the form to execute such Action on its creating thread
            this.Invoke(new Action(() =>
                {
                    label1.Text = text;
                }));
        });
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.186.83