With most current-gen computers having at least a dual core processor (and in some cases quad-core or Dual CPU), multi-threading is as important as ever to keep in mind when making an application.
1. Use threads for heavy computations.
This one is a basic concept. If you're on your application's main thread, and you have to perform some calculations that could potentially take a long time, always put them on a seperate thread, and use the dispatcher to send the answers back. If you don't, your application could begin to lag or freeze up until the calculations are done, which is very bad.
Actually, anything that might freeze up your application, stick that in a seperate thread. Keeping everything running smooth is your main priority. If your user thinks the program froze, there's a good chance they'll try to close it.
2. Use the dispatcher to send answers back to your main thread.
In WPF, each control in a window has a dispatcher. This gives you a link to the thread that manages this control, and ensures you full access to the control. However, know that commands you invoke through the dispatcher will not be executed immediately.
Based on the priority an invoke is given, the function to be executed is inserted into a queue to be executed at the proper time. Other threads generated in this queue are things such as rendering updates to the screen, databinding, input handling, and so on. Keep commands in your invoke limited to the commands that NEED to be invoked.
3. Not sure if you need to invoke? CheckAccess
Dispatchers have a hidden function (I have no idea why its hidden, because its useful). The CheckAccess function will tell your thread whether it has access to change a control. This is very useful if you have a function that updates a control, and you're not sure when it'll be called from the main thread or a side thread. Here is an example of only performing an invoke when it is required:
private delegate void VoidDelegate();
if (myTextBox.Dispatcher.CheckAccess())
myTextBox.Text = "Hello there!";
else
{
myTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Render, new VoidDelegate(delegate
{
myTextBox.Text = "Hello there!";
}));
}
The VoidDelegate declaration is something I use often for doing anonymous functions, so this also shows how you can call an invoke anonymously. BeginInvoke will allow your thread to continue past this section without waiting for it to complete, or Invoke will wait until your invoked section completes.
4. Leave enough time for your application to render.
This won't be a problem for most people, but if you're in an application that could theoretically want to render very fast, you need to be aware of how long it takes to render. I suppose profiling tools could do this, but since I don't have any here at work, I've just used the Guess & Test method.
I'm running an ultrasound application, that can generate images anywhere between 5 frames per second up to 150 frames per second. Telling my application to re-render 150 times in a second, while generating these images on the fly, as well as doing databinding across dual monitors, each displaying its own live window with the image, is somewhat absurd.
After finding a small hack online to allow me to quickly write to an Image's buffer, I can use InvalidateVisual to tell the Image to re-render. Up to about 30 frames per second, the picture seems smooth. This means the window is taking approximately 35 milliseconds to render. However, past this, I start to experience some lag in the picture. Past 60, I start to experience lag in the entire application. As I approach 150, many parts of the application stop responding. Since I want smooth images, I limit how fast I process my data to 30fps.