|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionI'm afraid to say that I am just one of those people, that unless I am doing something, I am bored. So now that I finally feel I have learnt tha basics of WPF, it is time to turn my attention to other matters. I have a long list of things that demand my attention such as (WCF/WF/CLR Via C# version 2 book), but I recently went for a new job (and got, but turned it down in the end) which required me to know a lot about threading. Whilst I consider myself to be pretty good with threading, I thought yeah I'm ok at threading, but I could always be better. So as a result of that I have decided to dedicate myself to writing a series of articles on threading in .NET. This series will undoubtely owe much to an excellent Visual Basic .NET Threading Handbook that I bought that is nicely filling the MSDN gaps for me and now you. I suspect this topic will range from simple to medium to advanced, and it will cover a lot of stuff that will be in MSDN, but I hope to give it my own spin also. So please forgive me if it does come across a bit MSDN like. I dont know the exact schedule, but it may end up being something like
I guess the best way is to just crack on. One note though before we start, I will be using C# and Visual Studio 2008. What I'm going to attempt to cover in this article will be This article will be all about how to thread different types of UIs Why Thread UIsI guess we have all seen some pretty cool UIs and some pretty bad ones in our lives. I know when I use a UI the first thing that makes me want to un-install something is the application being unresponsive. I have a rule if something is unresponsive, it gets removed, no questions. It’s out. So what could these software developers that made me un-install something have done differently? Well, with a little for thought and a little threading knowledge, this situation could have been avoided. I hope that at least a few of you have read the other articles in this series. If so is should come as no surprise to you, when I say these issues of unresponsive UIs could probably have been avoided if background tasks were run in background threads, leaving the UI to be responsive to further user interactions. It is be allowing background work to carry on, and updating the UI when appropriate (say when the work is done) that a responsive UI can be constructed. This article aims to show you a few techniques to work with to create UIs that
are able to deal with a single or n-many background tasks whilst maintaining
a responsive UI. I will be covering techniques for Winforms and WPF mainly but
will give you some pointers for working with Silverlight. Threading In WinFormsIn this section I am going to show you how to use threads within a WinForms
environment. This will typically be done with the The reason that the
It is great if this fits your needs, but for more finer detail control, you
should to spawn and manage your own threads. For this section of the article
though, I will just be using the But for now let's march on and look at some examples using the Firstly A Bad ExampleNow in order to understand the rest of this article is important to see a non working example, so as part of the code that this article provides I have provided a BAD example WinForms app. When you try and run this, you will see something like
Now lets look at the code that created this handled using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Threading.UI.Winforms
{
public partial class BackgroundWorkerBadExample : Form
{
public BackgroundWorkerBadExample()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
for (int i = 0; i < (int)e.Argument; i++)
{
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Completed background task");
}
private void btnGo_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(100);
}
}
}
The important part to note here is the Luckily we can fix this in a number of ways, which I will be describe below.
But before I show you how to fix this, let me just talk about how to work with
the The The following table outlines how to do various things with the
Some Now Some Better OptionsSo what I want to show you now are some options to marshall the background work to the UI thread. I have included 3 options OPTION 1 : Use BeginInvoke (works with all versions of .NET)try
{
for (int i = 0; i < (int)e.Argument; i++)
{
if (this.InvokeRequired)
{
this.Invoke(new EventHandler(delegate
{
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
}));
}
else
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
This is probably the oldest way to marshall to the UI thread, but its also the most explict and really shows whats going on, and I think aids readability. OPTION 2 : Use SynchonizationContext (works with .NET 2.0 and above)private SynchronizationContext context;
.....
.....
//set up the SynchronizationContext
context = SynchronizationContext.Current;
if (context == null)
{
context = new SynchronizationContext();
}
.....
.....
try
{
for (int i = 0; i < (int)e.Argument; i++)
{
context.Send(new SendOrPostCallback(delegate(object state)
{
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
}), null);
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
This version uses a .NET 2.0 available object called the OPTION 3 : Use Lambdas (works with .NET 3.0 and above)Now we could also go completely mad, and replace the use of the anonomous delegate with a lambda which would give us something like private SynchronizationContext context;
.....
.....
//set up the SynchronizationContext
context = SynchronizationContext.Current;
if (context == null)
{
context = new SynchronizationContext();
}
.....
.....
try
{
for (int i = 0; i < (int)e.Argument; i++)
{
context.Send(new SendOrPostCallback((s) =>
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString())
), null);
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
I guess it really depends on how happy your are with lambdas. I think they are ok for small tasks, but believe me I have seen them used in overload, and its not pretty. The next article will be very lambda intensive, as Task Parallel Library (TPL) seems to use loads of lambdas. When you run either of these options in the demo code, you will get a very simple form that shows something like
What About Reporting ProgressYou may of course want to report progress completed when using the using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace Threading.UI.Winforms
{
public partial class BackgroundWorkerReportingProgress : Form
{
private int factor = 0;
private SynchronizationContext context;
public BackgroundWorkerReportingProgress()
{
InitializeComponent();
//set up the SynchronizationContext
context = SynchronizationContext.Current;
if (context == null)
{
context = new SynchronizationContext();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
try
{
for (int i = 0; i < (int)e.Argument; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
context.Send(new SendOrPostCallback( (s) =>
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString())
), null);
//report progress
Thread.Sleep(1000);
worker.ReportProgress((100 / factor) * i + 1);
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
}
private void btnGo_Click(object sender, EventArgs e)
{
factor = 100;
backgroundWorker1.RunWorkerAsync(factor);
}
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Completed background task");
}
private void btnCancel_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
}
}
Its very simple, we just wire up the And to cancel the operation, we can simply call the I have attached a small demo project, which when run will look like the following:
Threading In WPFWPF is new to .NET 3.0, and I don't know how many of you are using this (Me I love it). The thing to note is that it's still produces code that are .NET and although a WPF app may look different from a WinForms app, some of the underlying plumbing is the same. Threading is one area, where the underlying idea is the same as WinForms. Recall "the one cardinal rule, and that is all controls must be accessed
using the thread that created them". Well this is the same in WPF. The
only difference is that we must use a WPF object known as the I have created a Anyway here is an example in WPF using the 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.Shapes;
using System.ComponentModel;
using System.Windows.Threading;
namespace Threading.UI.WPF
{
/// <summary>
/// Interaction logic for BackGroundWorker.xaml
/// </summary>
public partial class BackGroundWorkerWindow : Window
{
private BackgroundWorker worker = new BackgroundWorker();
public BackGroundWorkerWindow()
{
InitializeComponent();
//Do some work with the Background Worker that
//needs to update the UI.
//In this example we are using the System.Action delegate.
//Which encapsulates a a method that takes no params and
//returns no value.
//Action is a new in .NET 3.5
worker.DoWork += (s, e) =>
{
try
{
for (int i = 0; i < (int)e.Argument; i++)
{
if (!txtResults.CheckAccess())
{
Dispatcher.Invoke(DispatcherPriority.Send,
(Action)delegate
{
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
});
}
else
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
};
}
private void btnGo_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync(100);
}
}
}
This is very simliar to the WinForms example I showed earlier, but this time
we MUST use a WPFism, which is the WPF if (!txtResults.CheckAccess())
{
Dispatcher.Invoke(DispatcherPriority.Send,
(Action)delegate
{
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
});
}
else
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
If we now compare that with the 1st option I gave you when working with WinForms:
WinForms if (this.InvokeRequired)
{
this.Invoke(new EventHandler(delegate
{
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
}));
}
else
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
I also wanted to show you how to use a ThreadPool is WPF (this would be the
similiar in WinForms, just lose the WPF specific stuff, like ThreadPool UsageAttached is a small example that uses a ThreadPool (which I discussed in detail in part4 of this series). I have included 2 options which are as follows: OPTION 1 : Use LambdasThis example uses lambdas. try
{
for (int i = 0; i < 10; i++)
{
//CheckAccess(), which is rather strangely marked [Browsable(false)]
//checks to see if an invoke is required
//and where i respresents the State passed to the
//WaitCallback
if (!txtResults.CheckAccess())
{
//use a lambda, which represents the WaitCallback
//required by the ThreadPool.QueueUserWorkItem() method
ThreadPool.QueueUserWorkItem(waitCB =>
{
int state = (int)waitCB;
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
((Action)delegate
{
txtResults.Text += string.Format(
"processing {0}\r\n", state.ToString());
}));
}, i);
}
else
txtResults.Text += string.Format(
"processing {0}\r\n", i.ToString());
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
The important part here is the way that the state is obtained for the OPTION 2: Use More Explicit Syntaxtry
{
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), i);
}
}
catch (InvalidOperationException oex)
{
MessageBox.Show(oex.Message);
}
....
....
....
// This is called by the ThreadPool when the queued QueueUserWorkItem
// is run. This is slightly longer syntax than dealing with the Lambda/
// System.Action combo. But it is perhaps more readable and easier to
// follow/debug
private void ThreadProc(Object stateInfo)
{
//get the state object
int state = (int)stateInfo;
//CheckAccess(), which is rather strangely marked [Browsable(false)]
//checks to see if an invoke is required
if (!txtResults.CheckAccess())
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
((Action)delegate
{
txtResults.Text += string.Format(
"processing {0}\r\n", state.ToString());
}));
}
else
txtResults.Text += string.Format(
"processing {0}\r\n", state.ToString());
}
Although this syntax is longer than the lambda example, it obviously more explicit.
I think its a judgement call, if you are happy working with lambdas, go for it.
Threading In SilverlightThis section assumes you have the Silverlight 2.0 BETA installed. "Silverlight 2 brings support for threading to the browser. You can
either directly start new threads using System.Threading.Thread and System.Threading.ThreadPool,
or you can use the higher-level (and recommended) A lesser-known type that we introduced in beta 1 is System.Windows.Threading.Dispatcher.
This type lets you execute work on the UI thread - something that's useful when
you directly want to update the UI from a background thread. Since Silverlight
always has a single UI-thread, there is only a single disatcher instance per
Silverlight application. This instance is accessible via any Please note that you may not be able to find the dispatcher property via
intellisense. It's marked as an advanced property, so you either need to update
your VS settings to display advanced members, or you just need to ignore intellisense
and assume your code will in fact compile regardless of what intellisense implies.
The same goes for CheckAccess, which is actually marked as a member that should
never be displayed. The main reason these members aren't always visible is because
they shouldn't be as common as the other members on a DependencyObject. As I
mentioned before, you'll probably want to use a BackgroundWorker most of the
time instead. There are a couple of things to be aware of. The first is that we try to guard against cross-thread invocations when this would potentially be unsafe. For example, we don't allow you to call into the HTML DOM or a JavaScript function from a background thread. The reason for this is that both assume to be invoked on the UI thread. Breaking this assumption can lead to unexpected behavior, including browser crashes. The other thing to be aware of is creating deadlocks. Silverlight comes with primitives such as Monitor (encapsulated via the lock construct in C#) and ManualResetEvent which make it trivial to create a deadlock. A deadlock will cause most browsers to hang completely. While technically this isn't very different from some JavaScript that infinitely, it's often easier to accidentally create a deadlock than an infinite loop of code. For example, I've seen several people try to create a synchronous version of HttpWebRequest by letting the current thread wait for a ManualResetEvent to be notified by the response callback. HttpWebRequests however execute their callbacks on the UI thread, which means you have a deadlock right there. While ideally you avoid blocking the UI thread entirely, you should at least consider specifying timeouts when you use a synchronization object. For example, instead of the lock construct in C# (Monitor.Enter/Exit), consider using Monitor.TryEnter/Exit passing in a reasonable timeout, and instead of using ManualResetEvent's parameterless WaitOne, consider using one of the overloads." http://www.wilcob.com/Wilco/Silverlight/threading-in-silverlight.aspx What this all means is that we are able to do something like this to marshall threads to the UI thread in Silverlight. In this example I am creating a new thread and using a lambda to marshall to the correct UI thread. The 2nd option uses anonymous delegates, both are fine. var myThread = new Thread(() =>
{
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// OPTION 1 : Use lambda
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
txtResults.Dispatcher.BeginInvoke(() =>
txtResults.Text = "Updated from a non-UI thread.");
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// OPTION 2 : Use anonymous delegate
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//txtResults.Dispatcher.BeginInvoke(delegate
//{
// txtResults.Text = "Updated from a non-UI thread.";
//});
});
myThread.Start();
We're DoneWell that's all I wanted to say this time. I hope you liked the article, and that it helps you produce more responsive UIs. Could I just ask, if you liked this article could you please vote for it. I thank you very much. Possibly Next TimeIf I have enough time/patience/energy, next time we will be looking at the future of threading, which is the Task Parallel Library (TPL), which is a BETA at the moment. It is very complicated but looks pretty interesting. We shall see if time is on my side.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||