Thursday, June 28, 2007

How process asynchronous calls in Silverlight, by using custom Dispatcher

In current public alpha of Silverlight 1.1 there is no dispatcher. However there are STA and MTA. So, how to process long operations in background threads in MTA and update UI in STA? Well, let's write our own dispatcher. But this is not so simple, but it is possible. Let's start

How, actually works dispatcher? It's pumps system messages by using timer in Single Threaded Apartment. Let's write VeryLongMethod. It will be high precise π (PI) calculation. By passing all math stuff, I just point you to how I run the process

public static class BackgroundPiCalculator  { static Thread worker; public static void RunCalculationAsync(int digits, int delayMs) { P = 2 + (digits + 7) / 8; D = delayMs; worker = new Thread(new ThreadStart(calc)); worker.IsBackground = true; worker.Start(); } 

 





As you can see, I open new thread outside of MTA and run this long calculation. I added a couple of wait methods to be able to see the process. The next step is to create callbacks.




public static event EventHandler DoCalculation; public static event EventHandler<RunCalculationCompletedEventArgs> RunCalculationCompleted; public static event EventHandler<ProgressChangedEventArgs> ProgressChanged; 

 





Well. By now, if we'll subscribe to those events from the right thread (STA), we'll get them in the thread where we subscribed. Right? Let's do it.




 public void Page_Loaded(object o, EventArgs e) { // Required to initialize variables  InitializeComponent(); PICalculator.BackgroundPiCalculator.RunCalculationAsync(10000,100); PICalculator.BackgroundPiCalculator.ProgressChanged += new EventHandler<BackgroundPiCalculator.ProgressChangedEventArgs>(BackgroundPiCalculator_ProgressChanged); PICalculator.BackgroundPiCalculator.RunCalculationCompleted += new EventHandler<BackgroundPiCalculator.RunCalculationCompletedEventArgs>(BackgroundPiCalculator_RunCalculationCompleted); } 



 

void BackgroundPiCalculator_ProgressChanged(object sender, BackgroundPiCalculator.ProgressChangedEventArgs e) { result.Text = e.PreliminaryResult.ToString(); progress.Text = e.ProgressPercentage.ToString() + "%"; }

 








Well, compile and run to get the error "Invalid cross-thread access".



image



Hmmm, it comes from the other thread, but I subscribed to event in my thread, so what's the problem? Ah, the object I'm trying to use was created in other thread. This why I can not access it from my thread. So what to do?



Windows threats such cases by sending block event and moving the object to executing thread. It just pumps messages. Let's do it.



We create two queues - one for result text and the other for progress. Then when the data arrived we'll enqueue the message. Concurrently we'll create timer in STA to pump and dequeue those messages. The only timer can work in UI thread is HtmlTimer. Please notice, that this class is obsolete due to "This is not a high resolution timer and is not suitable for short-interval animations. A new timer type will be available in a future release.". Hey, there a new dispatchers will be available as well in a future release? I believe, that they are. So, let's create a timer, queues and handlers




HtmlTimer t = new HtmlTimer(); t.Interval = 100; t.Tick += new EventHandler(t_Tick); t.Start();




void t_Tick(object sender, EventArgs e) { while (resultMessages.Count > 0) { result.Text = resultMessages.Dequeue(); } while (progressMessages.Count > 0) { progress.Text = progressMessages.Dequeue(); } } 

 






 




Queue<string> resultMessages = new Queue<string>(); Queue<string> progressMessages = new Queue<string>();

 








And enqueue messages upon their arrival.




void BackgroundPiCalculator_ProgressChanged(object sender, BackgroundPiCalculator.ProgressChangedEventArgs e) { resultMessages.Enqueue(e.PreliminaryResult.ToString()); progressMessages.Enqueue(e.ProgressPercentage.ToString() + "%"); } 

 





We done. Now the calculations will be performed asynchronously and we'll get a feedback into our UI.



image



Well done, so what's next? Next, we'll enqueue delegates and invoke them into right thread, but all this in the next post. Have a nice weekend.



Source code for this article.

No comments: