WPF中Timer與DispatcherTimer類的區別


早上在某WPF群里吹水,突然有人問了一個問題,他想利用一個計時器Timer類,實時更新界面上的控件內容,但是一直遇到拋出異常:System.InvalidOperationException {"調用線程無法訪問此對象,因為另一個線程擁有該對象。"} 。

於是我就拖了兩個Label控件,在WPF上測試起來,代碼如下:

 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.Timers;
15
16 namespace TimerTest
17 {
18 /// <summary>
19 /// Interaction logic for MainWindow.xaml
20 /// </summary>
21 public partial class MainWindow : Window
22 {
23 private Timer aTimer = null;
24 public MainWindow()
25 {
26 InitializeComponent();
27
28 aTimer = new Timer();
29 aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
30 // Set the Interval to 5 seconds.
31 aTimer.Interval = 1000;
32 aTimer.Enabled = true;
33 aTimer.Start();
34 }
35
36 private void OnTimedEvent(object source, ElapsedEventArgs e)
37 {
38 timeLabel.Content = DateTime.Now.ToUniversalTime();
39 }
40 }
41 }

Debug的時候,發現在第38行的時候,timeLabel.Content 賦值的時候拋出了該異常

Google搜索了一下發現:”訪問 Windows 窗體控件本質上不是線程安全的。如果有兩個或多個線程操作某一控件的狀態,則可能會迫使該控件進入一種不一致的狀態。還可能出現其他與線程相關的 bug,包括爭用情況和死鎖。確保以線程安全方式訪問控件非常重要。“——來自MSDN http://msdn.microsoft.com/zh-cn/library/ms171728(en-us,VS.80).aspx

哎,在群上回答說了一句:子線程無法訪問界面線程的對象。被指沒學過C#(的確,我學C#就是在一周之內看完一本幾百頁的書),這句話說的的確不夠嚴謹,因為是有方法的,可以通過委托的方式進行訪問,同時,這個界面線程的對象是指控件,如Label,TextBox之類。之前都寫一個Qt程序的時候也遇到類似的問題,也是類似的情況:在子線程中試圖直接更新界面上的一張圖片,結果程序奔潰了,調試了很久才發現問題所在。

這時候我就想起來前陣子做過的一個練習,使用DispatcherTimer來實現更新。測試了一下,發現可行啊!!

 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.Timers;
15 using System.Windows.Threading;
16
17 namespace TimerTest
18 {
19 /// <summary>
20 /// Interaction logic for MainWindow.xaml
21 /// </summary>
22 public partial class MainWindow : Window
23 {
24 private DispatcherTimer dispatcherTimer = null;
25 public MainWindow()
26 {
27 InitializeComponent();
28
29 dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
30 dispatcherTimer.Tick += new EventHandler(OnTimedEvent);
31 dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
32 dispatcherTimer.Start();
33 }
34
35 private void OnTimedEvent(object sender, EventArgs e)
36 {
37 timeLabel.Content = DateTime.Now.ToUniversalTime();
38 }
39 }
40 }

於是好奇這兩者有什么不同?

仔細閱讀MSDN上的文檔后,可以得知:DispatcherTimer在界面線程中實現的,當然可以安全地訪問,修改界面內容。

If a System.Timers.Timer is used in a WPF application, it is worth noting that the System.Timers.Timer runs on a different thread then the user interface (UI) thread. In order to access objects on the user interface (UI) thread, it is necessary to post the operation onto the Dispatcher of the user interface (UI) thread using Invoke or BeginInvoke. Reasons for using a DispatcherTimer opposed to a System.Timers.Timer are that theDispatcherTimer runs on the same thread as the Dispatcher and a DispatcherPriority can be set on the DispatcherTimer.

呵呵,區別就是在這里了!!
附:感謝群里高手的指點,采用Timer,使用Invoke或者BeginInvoke的方式進行UI的更新的方式(好處在於:在DispatcherTimer里面執行等待動作或者時間過長,可能會導致UI假死):

 1 using System;
2 using System.Windows;
3 using System.Timers;
4 using System.Windows.Threading;
5
6 namespace TimerTest
7 {
8 /// <summary>
9 /// Interaction logic for MainWindow.xaml
10 /// </summary>
11 public partial class MainWindow : Window
12 {
13 private Timer aTimer = null;
14
15 private delegate void TimerDispatcherDelegate();
16
17 public MainWindow()
18 {
19 InitializeComponent();
20
21 aTimer = new Timer(1000);
22 aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
23 aTimer.Interval = 1000;
24 aTimer.Enabled = true;
25 }
26
27 private void OnTimedEvent(object sender, EventArgs e)
28 {
29 this.Dispatcher.Invoke(DispatcherPriority.Normal,
30 new TimerDispatcherDelegate(updateUI));
31 }
32
33 private void updateUI()
34 {
35 timeLabel.Content = DateTime.Now.ToUniversalTime();
36 }
37 }
38 }






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM