Click here to Skip to main content
Click here to Skip to main content
Go to top

Keep Your User Interface Responsive Easily Using a Coworker

, 8 Jul 2012
Rate this:
Please Sign up or sign in to vote.
An alternative approach to the new .NET async/await keywords to program asynchronously commands to make your user interface more responsive.

Introduction

In a previous article Paulo Zemek describes some limitations of the async/await model introduced in .NET 4.5. Inspired by that I created my own helper class, Coworker, to make it easy to keep the user interface (UI) responsive by inserting asynchronously running code blocks calls whenever an algorithm or single method call is potentially long-running. This solution makes it really easy to switch between code that has to be run synchronously with the user interface thread and processing that is better run asynchronously in the background. This is an alternative to the await/async solution that works for both WinForms and WPF applications from .NET version 3.5.

Background

No one likes an application that freezes its user interface when running long-running operation, but it is still challenging to write applications that does not do that. Below is a typical, but simplified, example of an event handler that will make some operations potentially taking significant time to complete:

private btnStart2_Click(object sender, RoutedEventArgs e)
{
   lboMessages.Items.Add("Operation started");

   for( i = 0; i < 10; i++) 
   {
      int result = DoLongOperation(i);

      lboMessages.Items.Add("Completed " + (i + 1) * 100 / 10 "%");

   }
}

The DoLongOperation may represent either an advanced mathematical algorithm or, perhaps more common an I/O or web service call, that at least occasionally, will take significant time to perform. The basic problem with this very common approach is that all work is performed in the user interface thread, making the whole application window to freeze during the processing. In addition, none of the progress messages will be shown until the method has ended.

How do you solve this problem? The .NET framework offers multiple option including BackgroundWorker, BeginInvoke on a delegate, ThreadPool and Task, but all of these solutions requires adding significant amounts of code to get the all invocations on the correct threads. This extra plumbing code makes it much harder to read and understand the code, and the risk of introducing defects by doing this is high.

Microsoft has recognized this to be a problem too and are therefore introducing the new async and await keywords in .NET 4.5 and 5.0 to alleviate this situation. However, to use these keywords you must break the interfaces of the involved methods. There is also strict rules for return values (only void, Task or Task<T> allowed). In this simplified example the code would be

private async void btnStart2_Click(object sender, RoutedEventArgs e) 
{ 
   lboMessages.Items.Add("Operation started"); 

   for (int i = 0; i < 10; i++)
   { 
      int result = await DoLongOperationAsync(i); 

      lboMessages.Items.Add("Completed " + (i + 1) * 100 / 10 + "%"); 
    } 
}

This example is deceptively simple, as most published examples of async/await are, hiding the fact that the original DoLongOperation has changed to DoLongOperationAsync which is required to be modified to create and return a Task<int>. In a real application the call to DoLongOperation may be done deeper down the call stack (e.g. in the data access layer). To introduce await there requires several breaking changes and imposes restrictions on the calling interfaces. With the solution presented below asynchronous blocks can easily be introduced anywhere in the call chain without breaking any interfaces.

Using the code

Introducing the Coworker

To make it easy to program enable asynchronous background processing I created a helper Coworker class. The first step to enable asynchronous processing is to delegate the work to the Coworker like this:

private void btnStart2_Click(object sender, RoutedEventArgs e) 
{ 
   Coworker.SyncBlock(() => { 

      lboMessages.Items.Add("Operation started"); 

      for (int i = 0; i < 10; i++) 
      { 
         int result = DoLongOperation(i); 

         lboMessages.Items.Add("Completed " + (i + 1) * 100 / 10 + "%"); 
       } 
   } 
}

The Coworker will run all the above code “synchronously”, i.e. still blocking the calling UI thread during the whole process. Yet we have just complicated the code and not won anything. However, now it is very easy to specify which parts to be run asynchronously, i.e. without blocking the UI thread, using an “asynchronous block” like this:

private void btnStart2_Click(object sender, RoutedEventArgs e) 
{ 
    Coworker.SyncBlock(() => { 

       lboMessages.Items.Add("Operation started"); 

       for (int i = 0; i < 10; i++) 
       { 
          using( Coworker.AsyncBlock() ) 
          { 
             int result = DoLongOperation(i); 
          } 

          lboMessages.Items.Add("Completed " + (i + 1) * 100 / 10 + "%"); 
      } 
   } 
}

The call to Coworker.AsyncBlock will release the user interface thread leaving it to serve other requests, effectively making the calls within the block asynchronous. When the using block eventually ends the user interface thread is captured again by the Coworker, allowing modification of the user interface elements without cross-threading call problems. You can freely choose which code statements that should be run in non-blocking mode and use variables as normal to pass information between the blocking and non-blocking parts as you like. It would not be a problem if DoLongOperation had an out argument.

Temporary switching back to synchronous mode

If there is a need to execute code that (potentially) needs to modify the user interface within a AsyncBlock, it is possible to use Coworker.SyncBlock() to temporarily switch back to synchronous mode. This can be performed anywhere in the calling chain, exemplified by this code in the DoLongOperation operation:

private int DoLongOperation(int i) 
{ 
   Thread.Sleep(500); 

   if( i > 8 ) 
   { 
      using( Coworker.SyncBlock() ) 
      { 
         lboMessages.Items.Add(("Value is greater than 8!"); 

      } 

   } 
   return i; 
}

Using the SyncBlock is a way for the method to tell that ”hey, I have some code that must update the user interface”.

Windows Presentation Foundation ICommand support

All examples above uses event handlers as entry points. If you use ICommand data-binding in WPF you can actually embed the code to Coworker.SyncBlock in a general reusable ICommand implementation as shown in the SyncBlockCommand in the attached demonstration code. In this way you easily disable an asynchronous command as long it is running.

Comparison with .NET 5 async/await

First of all the Coworker implementation does not require neither any new programming language keywords, nor the newest .NET framework (5.0). This code runs successfully using existing .NET 3.5 and 4.0 framework. Furthermore, to convert the synchronous code to the asynchronous responsive version no changes in the interfaces has to be done: no change of return type to Task<T> and no requirement to refactor out the code to be run asynchronously to a separate method, adding the Async suffix as recommended as a naming convention. You can still use methods with out and ref parameters, and unlike await you can also use AsyncBlock in catch and finally statements. This means that you are free to apply the Coworker.AsyncBlock and SyncBlock wherever you like in the call chain, for instance in the view, view model or data access layer, without changing the calling interfaces. However, you still have to think of the possibility of concurrent access to the same shared resources within asynchronous blocks though, but the same is true for awaited methods.

Implementation Details

How can this work?

The Coworker actually runs all code in a separate thread (currently obtained from the .NET ThreadPool), but blocks the user interface code during the synchronous parts making it safe to access the UI resources during these times. In comparison, when using async and await keywords execution switches between UI thread and a background thread. To enable the latter the compiler must genererate a significant amount of code to allow the execution state, including all local variables to be transfered back and forth between threads.

To complicate matters, both Windows Forms and WPF perform checks to verify that the UI resources are accessed only from the UI thread. To make Coworker work we have to work around these checks.

In WinForms the cross-threading checks are only performed in debugging mode. To avoid InvalidOperationExceptions in this mode we must disable these checks. This is simply done by including the following line in some initializing code:

Control.CheckForIllegalCrossThreadCalls = false; 

In WPF things are a bit more complicated since all DispatcherObject derived objects in the user interface is bound to the Dispatcher thread they are created on. To circumvent the checkings made by the user interface objects Coworker temporarily changes the calling Dispatcher’s thread binding during blocking calls, using this “hack” accessing a private field using reflection:

private static readonly FieldInfo dispatcherThreadField = typeof(Dispatcher).GetField("_dispatcherThread", BindingFlags.NonPublic | BindingFlags.Instance); 
private object oldDispatcherThread; 
private void SetDispatcher() 
{ 
   if (dispatcher != null) 
   { 
      oldDispatcherThread = dispatcherThreadField.GetValue(dispatcher); 
      dispatcherThreadField.SetValue(dispatcher, Thread.CurrentThread); 
   } 
}

Due to this hack it is not possible to use it in partial-trust applications. There is also a risk that Microsoft changes the internal implementation in future versions of .NET.

Conclusions

This article demonstrated an approach to keeping the user interface responsive without complicating the code much and not using the new async and await keywords. With this approach the responsiveness can be increased in existing applications without much change of the code.

Disclaimer

This is just a proof-of-concept article. Before using it in production code I would suggest more considerations about the usage of “hack” to access a hidden private Dispatcher field to circumvent the WPF thread-checking and other issues due to the fact that code is actually not run on the UI thread. Code that requires to be run in a single threaded apartment (STA) must still be delegated to the UI thread itself. More testing is required.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Henrik Jonsson
Software Developer
Sweden Sweden
Henrik Jonsson is a Microsoft Professional Certified Windows Developer (MCPD) that currently works as an IT consultant in Västerås, Sweden.
 
Henrik has worked in several small and large software development projects in various roles such as architect, developer, CM and tester.
 
He regularly reads The Code Project articles to keep updated about .NET development and get new ideas. He has contributed with articles presenting some useful libraries for Undo/Redo, Dynamic Linq Sorting and a Silverlight 5 MultiBinding solution.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberHerre Kuijpers10-Mar-14 22:01 
GeneralMy vote of 5 Pinmembermanoj kumar choubey15-Jan-13 22:01 
QuestionMy vote of 5 Pinmembernandixxp12-Jan-13 3:36 
GeneralMy vote of 5 PinprotectorPete O'Hanlon9-Jul-12 7:18 
QuestionA possibly confusing sample PinprotectorPete O'Hanlon9-Jul-12 1:48 
AnswerRe: A possibly confusing sample PinmemberHenrik Jonsson9-Jul-12 9:42 
Hi,
 
thanks for the high vote and the feedback.
 
As you write there is a possibility to missinterpret the AsyncBlock as something that is run asynchronous ("fire and forget") in respect to the calling code but that is not the case. For the Coworker, "Async" essentially means non-blocking of UI the thread.
 
When you run the demo you will see that the messages are shown only after each DoLongOperation has finished. At the same time you are able to start several concurrent operations since the UI is not blocked. Perhaps the example would be somewhat clearer if I used the result in the message below? Or should I rename the AsyncBlock to something like "NonUIBlockingBlock"?
 
The whole point with the Coworker is that you easily can switch between code that has to be run on the UI thread ("synchronous", i.e. mostly updates of the view) and code that is better run in "background" ("asynchronous", i.e. potentially long-running calculations and I/O operations) without blocking the UI, and that without any callbacks, new keywords or compiler tricks. Despite the switching you can still write and read the code as normal sequential code. Legacy code not considering UI blocking should be straightforward to upgrade to more responsive code using this helper class.
 
I welcome more feedback from anyone on this article.
GeneralRe: A possibly confusing sample PinprotectorPete O'Hanlon9-Jul-12 10:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140922.1 | Last Updated 8 Jul 2012
Article Copyright 2012 by Henrik Jonsson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid