Tuesday, August 26, 2008

[Multi-Threading] Syncing between threads by SynchronizationContext

-----------------------------------------
Download source code package for this article:

If you can't see/download the zip file, please try to use TOR. TOR is a great tool that can break the GFW and let you access any website that prohibited by GFW.
http://vidalia-project.net/download.php
-----------------------------------------

I'd like to offer a series of tech articles on Multi-Threading which is a hot topic these days. Actually, I have a tech talk weeks later. So I'm trying to introduce my learning on this topic to our group first :) Please don't hesitate to give me suggestions to improve my knowledge.


Today's topic is syncing between threads by SynchronizationContext.

As you know, from MFC, WinForm to WPF, UI element can't be accessed from a thread other than UI thread. If you try to do this, you will meet an assertion, or exception. For WPF applications, there will be two threads. One is rendering thread, the other is UI thread. Rendering thread is responsible for rendering UI elements to graphics card, while UI thread is for managing UI elements, and do application logic, etc. MSDN says that you can only access UI elements from UI thread. So, how can we delegate a task which will accessing UI element from a background thread? Since we need to update UI while doing background work, reporting progress or the completion of the work. SynchronizationContext can help us.

UI thread works on a work item queue mode. All works queued in a queue. UI thread select a work item which has the highest priority in the queue when the thread finished a work item. Because work items will be executed within UI thread, a big work item can block UI operations. So make work items small enough, don't do heavy work in it. That's why we need a background worker thread.

SynchronizationContext provides Post() and Send(). You can call them from your background thread, and delegate methods passed to them will be executed within UI thread. Delegate method passed to Post() will be executed asynchronously, while to Send(), the caller of Send() will wait until the delegate method returned.

DIG DEEPER: We can imaging the internal work of Post() or Send(), they will obtain the lock of the queue (work item queue) first, then insert the work item to the queue, and then release the lock.

Please find sample code at the end of this article. Firstly, we create a background thread in Window1() which is the initialization method of the application. We use the BeginInvoke() of the delegate type HeavyBackgroundWorkDelegate. The background thread will be created and started. AsyncCallback() is the handler of completion of background thread. Please note, it will be called from the background thread. In HeavyBackgroundWork(), the entry of background thread, we access UI element by synchronizationContext.Post().

From the debug ourput of the application, we can see, AsyncCallback is in the same thread as background, while UI thread is a different thread.

-------------------
UI thread: 10
Background thread: 6
HeavyBackgroundWork finished.
AsyncCallback is called from thread: 6
----------------------


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Diagnostics;

namespace Async_SynchronizationContext
{
///
/// Interaction logic for Window1.xaml
///
public partial class Window1 : Window
{
private delegate void HeavyBackgroundWorkDelegate();
private SynchronizationContext synchronizationContext;

private void HeavyBackgroundWork()
{
Debug.WriteLine("Background thread: " + Thread.CurrentThread.ManagedThreadId);

// To simulate a time-cost work.
Thread.Sleep(1000);

synchronizationContext.Post(new SendOrPostCallback(delegate(object param)
{
// We have to access TextInfo by synchronizationContext, otherwise
// you will meet an exception.
TextInfo.Text = "HeavyBackgroundWork() has been finished.";
}),
null);

Debug.WriteLine("HeavyBackgroundWork finished.");
}

public void AsyncCallback(IAsyncResult ar)
{
// This method will be called from HeavyBackgroundWork thread, not UI thread.
Debug.WriteLine("AsyncCallback is called from thread: " + Thread.CurrentThread.ManagedThreadId);
}

public Window1()
{
InitializeComponent();

synchronizationContext = SynchronizationContext.Current;

Debug.WriteLine("UI thread: " + Thread.CurrentThread.ManagedThreadId);
HeavyBackgroundWorkDelegate backgroundWork = new HeavyBackgroundWorkDelegate(HeavyBackgroundWork);
backgroundWork.BeginInvoke(AsyncCallback, null);
}
}
}
Post a Comment