【Code】C# 元件Invoke的方法

First Post:

Last Update:

Word Count:
1k

Read Time:
4 min

前言

記錄一下C#中可以在Thread中存取Controls的方法

C# 元件執行程式

C# WindowsForm中我們可以把要執行的程式寫在元件的事件(Event)中,
例如button1按下後可以彈出MessageBox的程式為

1
2
3
4
5
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Hello world");
//MessageBox.Show("Hello world","Here is the test", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

如果我們要把Hello world顯示在TextBox1上,那程式就會是

1
2
3
4
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "Hello world";
}

需要時間的程式

現在考慮一個ConsoleApplication
要把數字 i = 0 ~ 9999一行一行地顯示在Console上,
這很簡單 :

1
2
3
4
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(i);
}

可是如果在WindowsForm中要把 i = 0 ~ 9999一行一行地顯示在textBox1上呢 ?

1
2
3
4
5
for (int i = 0 ; i < 10000; i++)
{
textBox1.AppendText(i.ToString());
textBox1.AppendText(Environment.NewLine);
}

然後把這程式放到Button1內 :
1
2
3
4
5
6
7
8
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0 ; i < 10000; i++)
{
textBox1.AppendText(i.ToString());
textBox1.AppendText(Environment.NewLine);
}
}

好像很簡單對吧 ? 但事實不是這樣,當你執行後你會發現你的WindowsForm根本動不了,過了一段時間後WindowsForm可以動,然後數字 0 ~ 9999會突然出現在textBox1上,可是現在我想要的結果是數字會像在ConsoleApplication上一行一行地顯示在TextBox上,而不是這樣突然出現,何況程式還會突然卡死。

原因

UI Thread (Main Thread)

WindowsForm在不使用其他Thread的時候在程式打開(打開.exe檔)的一刻到程式關掉時,所有事都會發生在UI Thread上,例如for loop,Thread.Sleep(),Button按鍵等等,甚至包括WindowsForm本身的上下左右移動,也就是說當你按下button1時,程式就會進入for loop,而此時for loop就是發生在UI Thread上,直到for loop結束,UI Thread才會從for loop跑出來,可是在for loop結束之前,你的for loop都無法跑出來,也就不能對WindowsForm進行住何操作,而數字顯示在textBox1上這件事也是發生在UI Thread上,但此時UI Thread還在for loop當中,所以這就是為甚麼你的程式不能移動,也不會馬上顯示數字。

解決方法

Thread

這邊我們會想到可以建立一個新的Thread,用這個Thread去執行for loop,具體方法如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
void RunLoop()
{
for (int i = 0 ; i < 10000; i++)
{
textBox1.AppendText(i.ToString());
textBox1.AppendText(Environment.NewLine);
}
}

private void button1_Click(object sender, EventArgs e)
{
new Thread(RunLoop).Start();
}

看上去合理的程式,執行看看,又出現問題了,IDE會直接跟你說跨執行緒作業無效。

原因

新的Thread不能直接存取另外一個Thread,需要再加上一些東西,可以使用delegate或MethodInvoke的方法解決,這邊我提供一個我自己常用的方法,就是直接對元件Invoke

對元件直接Invoke

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void RunLoop()
{
for (int i = 0 ; i < 10000; i++)
{
textBox1.Invoke(new Action(() =>
{
textBox1.AppendText(i.ToString());
textBox1.AppendText(Environment.NewLine);
}));
//或者以下寫法
//textBox1.Invoke(new Action(() => textBox1.AppendText(i.ToString())));
//textBox1.Invoke(new Action(() => textBox1.AppendText(Environment.NewLine)));
}
}

private void button1_Click(object sender, EventArgs e)
{
new Thread(RunLoop).Start();
}

如此一來,數字就可以一行一行顯示

傳參數

一個參數

現在讓for loop的最大數字可控 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void RunLoop(object o)
{
for (int i = 0 ; i < (int)o; i++)
{
textBox1.Invoke(new Action(() =>
{
textBox1.AppendText(i.ToString());
textBox1.AppendText(Environment.NewLine);
}));
//或者以下寫法
//textBox1.Invoke(new Action(() => textBox1.AppendText(i.ToString())));
//textBox1.Invoke(new Action(() => textBox1.AppendText(Environment.NewLine)));
}
}

private void button1_Click(object sender, EventArgs e)
{
object o = 10;
new Thread(RunLoop).Start();
}

多個參數

現在讓for loop的最大數字可控且在指定TextBox顯示 :

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
void RunLoop(object o)
{
object[] obj_array = (object[])o;
int max = (int)obj_array[0];
TextBox textbox = (TextBox)obj_array[1];
//TextBox textbox = obj_array[1] as TextBox;

for (int i = 0 ; i < (int)o; i++)
{
textbox.Invoke(new Action(() =>
{
textbox.AppendText(i.ToString());
textbox.AppendText(Environment.NewLine);
}));
//或者以下寫法
//textbox.Invoke(new Action(() => textbox.AppendText(i.ToString())));
//textbox.Invoke(new Action(() => textbox.AppendText(Environment.NewLine)));
}
}

private void button1_Click(object sender, EventArgs e)
{
int max = 100;
new Thread(new ParameterizedThreadStart(RunLoop)).Start(new object[]
{
max, textBox1
});
}