2018/2/13

【C#】關於UI跨執行緒控制元件的問題

情境說明:在視窗介面開發常常會為了避免視窗凍結而將執行較久的函數用執行緒(Threading)去執行,若過程中需要把一些資訊丟到視窗元件上或是從視窗元件取得特定資料,就會發生跨執行緒存取權限的問題(=> 使用的執行緒與建立控制項的執行緒不同)。

解決方法:透過C# Delegate(委派)機制把工作丟給另個執行緒(控制元件的執行緒)代為處理

委派的寫法其實很簡單,其實就是幫元件寫個CallBack Function,在函數中首先先判斷元件是否在同個執行緒,若不是則呼叫CallBack Function(底下範例的if區塊),如果是則看需求把動作完成即可
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
delegate void TextBoxAppendCallback(TextBox Ctl, string Str);
public void TextBoxAppend(TextBox Ctl, string Str)
{
    if (Ctl.InvokeRequired)
    {
        TextBoxAppendCallback d = new TextBoxAppendCallback(TextBoxAppend);
        this.Invoke(d, new object[] { Ctl, Str });
    }
    else
    {
        Ctl.AppendText(Str);
    }
}
若只有幾個地方需要用到元件存取,可以考慮偷懶一點的寫法
1
2
3
4
this.Invoke((MethodInvoker)delegate()
{
    textBox1.AppendText("Hello, Sleep 3 Seconds ..." + Environment.NewLine);
});

完整範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public partial class Form1 : Form
{
	public Form1()
	{
		InitializeComponent();
	}

	delegate void TextBoxAppendCallback(TextBox Ctl, string Str);
	public void TextBoxAppend(TextBox Ctl, string Str)
	{
		if (Ctl.InvokeRequired)
		{
			TextBoxAppendCallback d = new TextBoxAppendCallback(TextBoxAppend);
			this.Invoke(d, new object[] { Ctl, Str });
		}
		else
		{
			Ctl.AppendText(Str);
		}
	}

	private void Test()
	{
		this.Invoke((MethodInvoker)delegate()
		{
			textBox1.AppendText("Hello, Sleep 3 Seconds ..." + Environment.NewLine);
		});
		Thread.Sleep(3000);
		this.Invoke((MethodInvoker)delegate()
		{
			textBox1.AppendText("I'm Back, Oh Wait One More Second, Please ..." + Environment.NewLine);
		});
		Thread.Sleep(1000);
		TextBoxAppend(textBox1, "Everything Is Done" + Environment.NewLine);
	}

	private void button1_Click(object sender, EventArgs e)
	{
		var thread = new Thread(Test);
		thread.Start();
	}
}