If you are working with WinForms or WPF, you will more than likely run into some long running operation that you would like to run in a new thread. A novice may actually try and create a new Thread, which is ok, but that means you are responsible for the entire lifecycle of your new thread. Which gets tricky.
A better approach would be to use the ThreadPool
or use a BackgroundWorker
component which uses the ThreadPool
beneath the surface.
However, even using these approaches, the cardinal rule is that the control is owned by 1 thread, the thread that created the controls. That is typically the UI thread. So when you try and update the controls from a background thread, you will run into problems.
This code demonstrates the problem with cross thread calls:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Windows;
6: using System.Windows.Controls;
7: using System.Windows.Data;
8: using System.Windows.Documents;
9: using System.Windows.Input;
10: using System.Windows.Media;
11: using System.Windows.Media.Imaging;
12: using System.Windows.Navigation;
13: using System.Windows.Shapes;
14: using System.ComponentModel;
15: using System.Windows.Threading;
16:
17: namespace BackgroundThread
18: {
19:
20:
21: public partial class Window1 : Window
22: {
23: private Int32 currentCount = 0;
24: private Int32 maxCount = 500;
25: private float factor = 0;
26:
27: public Window1()
28: {
29: InitializeComponent();
30:
31: }
32:
33: private void btnGo_Click(object sender, RoutedEventArgs e)
34: {
35: factor = (float)100 / maxCount;
36:
37: BackgroundWorker bgWorker = new BackgroundWorker();
38: bgWorker.WorkerReportsProgress = true;
39: bgWorker.WorkerSupportsCancellation = false;
40:
41:
42: bgWorker.DoWork += (s2, e2) =>
43: {
44: for (currentCount = 0;
45: currentCount < maxCount; currentCount++)
46: {
47: lstItems.Items.Add(
48: String.Format("Count {0}", currentCount));
49: }
50: };
51:
52:
53: bgWorker.ProgressChanged += (s3, e3) =>
54: {
55: pgbar.Value = e3.ProgressPercentage;
56: };
57:
58: bgWorker.RunWorkerAsync();
59:
60: }
61: }
62: }
Which when run will result in the following:
So how can we fix this, well we could use the Dispatcher.Invoke
around the offending items, but perhaps a more elegant solution may be to use a extension method.
1: public static class WPFThreadingExtensions
2: {
3: 4: 5: 6: 7: 8: 9: 10: public static void InvokeIfRequired(
11: this DispatcherObject control,
12: Action methodcall,
13: DispatcherPriority priorityForCall)
14: {
15:
16: if (control.Dispatcher.Thread != Thread.CurrentThread)
17: control.Dispatcher.Invoke(priorityForCall, methodcall);
18: else
19: methodcall();
20: }
21: }
Which we can then use in our code as simply as follows:
1: factor = (float)100 / maxCount;
2:
3: BackgroundWorker bgWorker = new BackgroundWorker();
4: bgWorker.WorkerReportsProgress = true;
5: bgWorker.WorkerSupportsCancellation = false;
6:
7:
8: bgWorker.DoWork += (s2, e2) =>
9: {
10: for (currentCount = 0;
11: currentCount < maxCount; currentCount++)
12: {
13:
14: this.InvokeIfRequired(() =>
15: {
16: lstItems.Items.Add(
17: String.Format("Count {0}", currentCount));
18: },
19: DispatcherPriority.Background);
20:
21: bgWorker.ReportProgress((int)(factor * (currentCount + 1)));
22:
23: }
24: };
25:
26:
27: bgWorker.ProgressChanged += (s3, e3) =>
28: {
29: this.InvokeIfRequired(() =>
30: {
31: pgbar.Value = e3.ProgressPercentage;
32: },
33: DispatcherPriority.Background);
34:
35:
36: };
37:
38: bgWorker.RunWorkerAsync();
39:
40: }
Which when run allows cross threaded calls to be marshaled to the correct Dispatcher
object.
Hope this helps!