如何在不同的執行緒中安全地修改 Window Form Control
問題出處:Windows Workflow Foundation 新一代工作流程開發實務 Page 23 範例
由於工作流程與介面控制項是執行在不同的執行緒上,因此當工作流程完成後要將結果顯示在 Window Form Control 時,必須透過代理 (delegate) 的方式存取,但此程式卻會造成當機,詳細請參考後面程式的說明
而解決方法也相當簡單,只要在執行 this.Invoke(...) 之前把主執行緒叫醒就可以了,也就是將程式改成下面的方式就萬事 OK 了
由於工作流程與介面控制項是執行在不同的執行緒上,因此當工作流程完成後要將結果顯示在 Window Form Control 時,必須透過代理 (delegate) 的方式存取,但此程式卻會造成當機,詳細請參考後面程式的說明
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Workflow.Activities; using System.Workflow.ComponentModel; using System.Workflow.Runtime; using System.Threading; using FirstWF; namespace WindowClient { public partial class StartFlow : Form { public StartFlow() { InitializeComponent(); } // 利用 Event 提供事件處理程式完成後,返回主程式的機制 private AutoResetEvent WaitHandle = new AutoResetEvent(false); delegate void SetTextCallback(string Message); private void Button1_Click(object sender, EventArgs e) { using (WorkflowRuntime workflowRuntime = new WorkflowRuntime()) { // 設定 Workflow 的事件處理常式 workflowRuntime.WorkflowCompleted += new EventHandler看到問題了嗎?當在 SetText 中執行 this.Invoke(...) 時,會等到主執行緒執行完代理的 SetText 才會返回。但問題是此時主執行緒已進入 Suspend 的模式,因此就造成 Deadlock 當掉了。(OnWorkflowCompleted); workflowRuntime.WorkflowTerminated += new EventHandler (OnWorkflowTerminated); int FirstNumber, SecondNumber; FirstNumber = int.Parse(txtFirstNumber.Text); SecondNumber = int.Parse(txtSecondNumber.Text); // 將取得的參數放入 Dictionary 中傳入工作流程 Dictionary parameters = new Dictionary (); parameters.Add("FirstNumber", FirstNumber); parameters.Add("SecondNumber", SecondNumber); WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(FirstWF.GetMax), parameters); instance.Start(); // 啟動 Workflow 後,主執行緒即進入 Suspend 模式等待 WaitHandle.WaitOne(); } } private void OnWorkflowTerminated(object sender, WorkflowTerminatedEventArgs e) { SetText(e.Exception.Message); WaitHandle.Set(); } private void OnWorkflowCompleted(object sender, WorkflowCompletedEventArgs e) { // Workflow 完成後,顯示結果 SetText(String.Format("較大的數字為:{0}", e.OutputParameters["Result"])); WaitHandle.Set(); } private void SetText(string Message) { // 檢查是否在不同的執行緒 if (this.lblMessage.InvokeRequired) { // 設定新的代理程式 SetTextCallback d = new SetTextCallback(SetText); // 將程式丟到主執行緒的 Queue 等待執行 this.Invoke(d, new object[] { Message }); } else { this.lblMessage.Text = Message; } } } }
而解決方法也相當簡單,只要在執行 this.Invoke(...) 之前把主執行緒叫醒就可以了,也就是將程式改成下面的方式就萬事 OK 了
private void OnWorkflowTerminated(object sender, WorkflowTerminatedEventArgs e) { WaitHandle.Set(); SetText(e.Exception.Message); } private void OnWorkflowCompleted(object sender, WorkflowCompletedEventArgs e) { WaitHandle.Set(); SetText(String.Format("較大的數字為:{0}", e.OutputParameters["Result"])); }
留言