如何在不同的執行緒中安全地修改 Window Form Control

問題出處:Windows Workflow Foundation 新一代工作流程開發實務 Page 23 範例

由於工作流程與介面控制項是執行在不同的執行緒上,因此當工作流程完成後要將結果顯示在 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(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;
            }
        }
    }
}
看到問題了嗎?當在 SetText 中執行 this.Invoke(...) 時,會等到主執行緒執行完代理的 SetText 才會返回。但問題是此時主執行緒已進入 Suspend 的模式,因此就造成 Deadlock 當掉了。

而解決方法也相當簡單,只要在執行 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"]));
        }

留言

這個網誌中的熱門文章

Linux 批次檔的寫法

【分享】如何顯示 Debug Message

[分享] Visual Studio 遠端偵錯