有用的 WPF 线程扩展方法






4.20/5 (2投票s)
有用的 WPF 线程扩展方法
如果您正在使用 WinForms 或 WPF,您很可能会遇到一些需要在新线程中运行的耗时操作。 初学者可能会尝试创建新的线程,这也可以,但这意味着您需要负责新线程的整个生命周期,这会变得很棘手。
更好的方法是使用 ThreadPool
或使用 BackgroundWorker
组件,该组件在底层使用 ThreadPool
。
然而,即使使用这些方法,最重要的规则是控件由一个线程拥有,即创建控件的线程。 通常是 UI 线程。 因此,当您尝试从后台线程更新控件时,会遇到问题。
这段代码演示了跨线程调用的问题
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: //DoWork
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: //ProgressChanged
53: bgWorker.ProgressChanged += (s3, e3) =>
54: {
55: pgbar.Value = e3.ProgressPercentage;
56: };
57:
58: bgWorker.RunWorkerAsync();
59:
60: }
61: }
62: }
运行后将产生以下结果
那么我们如何解决这个问题呢? 我们可以使用 Dispatcher.Invoke
围绕违规项目,但也许更优雅的解决方案是使用扩展方法。
1: public static class WPFThreadingExtensions
2: {
3: /// <summary>
4: /// Simple helper extension method to marshall to correct
5: /// thread if its required
6: /// </summary>
7: /// <param name="""control""">The source control</param>
8: /// <param name="""methodcall""">The method to call</param>
9: /// <param name="""priorityForCall""">The thread priority</param>
10: public static void InvokeIfRequired(
11: this DispatcherObject control,
12: Action methodcall,
13: DispatcherPriority priorityForCall)
14: {
15: //see if we need to Invoke call to Dispatcher thread
16: if (control.Dispatcher.Thread != Thread.CurrentThread)
17: control.Dispatcher.Invoke(priorityForCall, methodcall);
18: else
19: methodcall();
20: }
21: }
然后我们可以在代码中像下面这样简单地使用它
1: factor = (float)100 / maxCount;
2:
3: BackgroundWorker bgWorker = new BackgroundWorker();
4: bgWorker.WorkerReportsProgress = true;
5: bgWorker.WorkerSupportsCancellation = false;
6:
7: //DoWork
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: //ProgressChanged
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: }
运行后,允许将跨线程调用编排到正确的 Dispatcher
对象。
希望这有帮助!