目錄
1. 使用?Control.Invoke?或?Control.BeginInvoke(Windows Forms)
2. 使用?Dispatcher.Invoke?或?Dispatcher.BeginInvoke(WPF)
3. 使用?SynchronizationContext
?
桌面應用程序(如 Windows Forms 或 WPF)中,UI 操作必須由主線程(也稱 UI 線程)執行。如果嘗試從非 UI 線程直接更新 UI 元素,通常會引發異常或導致不可預測的行為。
Thread?類本身無法直接更新 UI,但可以通過以下方法將操作委托給 UI 線程來實現安全的 UI 更新。
1. 使用?Control.Invoke?或?Control.BeginInvoke(Windows Forms)
在 Windows Forms 應用程序中,可以使用 Control.Invoke?或 Control.BeginInvoke?方法將代碼調度到 UI 線程。
示例代碼:
using System;
using System.Reflection.Emit;
using System.Threading;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;class Program : Form
{private Button button;private Label label;public Program(){button = new Button { Text = "Start Thread", Dock = DockStyle.Top };label = new Label { Text = "Waiting...", Dock = DockStyle.Fill };button.Click += Button_Click;Controls.Add(label);Controls.Add(button);}private void Button_Click(object sender, EventArgs e){Thread thread = new Thread(UpdateLabel);thread.Start();}private void UpdateLabel(){for (int i = 0; i < 10; i++){// 檢查是否需要調用 Invokeif (label.InvokeRequired){// 使用 Invoke 將操作調度到 UI 線程label.Invoke(new Action(() => label.Text = $"Count: {i}"));}else{// 如果當前線程是 UI 線程,則直接更新label.Text = $"Count: {i}";}Thread.Sleep(500); // 模擬工作}}[STAThread]static void Main(){Application.EnableVisualStyles();Application.Run(new Program());}
}
解釋:
- InvokeRequired:檢查當前線程是否是創建控件的線程(即 UI 線程)。如果不是,則需要通過?Invoke?或?BeginInvoke?調度到 UI 線程。
- Invoke:同步執行指定的操作,等待操作完成后再繼續。
- BeginInvoke:異步執行指定的操作,不阻塞當前線程。
?
輸出效果:
點擊按鈕后,label?的文本會每 500 毫秒更新一次,顯示當前計數值。
2. 使用?Dispatcher.Invoke?或?Dispatcher.BeginInvoke(WPF)
在 WPF 應用程序中,可以使用 Dispatcher?對象將操作調度到 UI 線程。
示例代碼:
using System;
using System.Reflection.Metadata;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using static System.Net.Mime.MediaTypeNames;class MainWindow : Window
{private Button button;private TextBlock textBlock;public MainWindow(){button = new Button { Content = "Start Thread" };textBlock = new TextBlock { Text = "Waiting..." };button.Click += Button_Click;var stackPanel = new StackPanel();stackPanel.Children.Add(button);stackPanel.Children.Add(textBlock);Content = stackPanel;}private void Button_Click(object sender, RoutedEventArgs e){Thread thread = new Thread(UpdateTextBlock);thread.Start();}private void UpdateTextBlock(){for (int i = 0; i < 10; i++){// 使用 Dispatcher 將操作調度到 UI 線程textBlock.Dispatcher.Invoke(() => textBlock.Text = $"Count: {i}");Thread.Sleep(500); // 模擬工作}}
}class Program
{[STAThread]static void Main(){var app = new Application();app.Run(new MainWindow());}
}
解釋:
- Dispatcher.Invoke:同步執行指定的操作,確保操作在 UI 線程上運行。
- Dispatcher.BeginInvoke:異步執行指定的操作,不阻塞當前線程。
輸出效果:
點擊按鈕后,TextBlock?的文本會每 500 毫秒更新一次,顯示當前計數值。
3. 使用?SynchronizationContext
SynchronizationContext?是一種更通用的方式,適用于 Windows Forms 和 WPF,甚至其他框架(如 ASP.NET)。它允許你捕獲當前線程的上下文,并在需要時將其用于調度操作。
示例代碼:
using System;
using System.Reflection.Emit;
using System.Threading;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;class Program : Form
{private Button button;private Label label;private SynchronizationContext _uiContext;public Program(){button = new Button { Text = "Start Thread", Dock = DockStyle.Top };label = new Label { Text = "Waiting...", Dock = DockStyle.Fill };button.Click += Button_Click;Controls.Add(label);Controls.Add(button);// 捕獲 UI 線程的上下文_uiContext = SynchronizationContext.Current;}private void Button_Click(object sender, EventArgs e){Thread thread = new Thread(UpdateLabel);thread.Start();}private void UpdateLabel(){for (int i = 0; i < 10; i++){// 使用 SynchronizationContext 將操作調度到 UI 線程_uiContext.Post(_ => label.Text = $"Count: {i}", null);Thread.Sleep(500); // 模擬工作}}[STAThread]static void Main(){Application.EnableVisualStyles();Application.Run(new Program());}
}
解釋:
- SynchronizationContext.Current:捕獲當前線程的上下文(通常是 UI 線程的上下文)。
- Post:異步執行指定的操作。
- Send:同步執行指定的操作。
輸出效果:
點擊按鈕后,label?的文本會每 500 毫秒更新一次,顯示當前計數值。
?
?
?