CodeProject上有篇文章An Alternate Way of Writing a Multithreaded GUI in C#
本意是Alternate Way 另外一種方法,後來莫名其妙的被轉載成中文變了題目,最高效的方法。
CheckForIllegalCrossThreadCalls和control.Invoke有什麼不同,哪個更好用,更高效呢?
占在任何角度講,都不要使用CheckForIllegalCrossThreadCalls,即便他運行和代碼編寫的確實比Invoke效率高。
感興趣的,可以參考我後面貼出的代碼來測試對比一下兩者的不同。這裡我只簡單說一下結論:
1、性能CheckForIllegalCrossThreadCalls=false時比invoke高,而且代碼比較優雅。
測試時間如下:
Button1 ---------------------------
00:00:01.0760900
00:00:01.0771200
Button2 --------------------------
00:00:01.0812499
00:00:01.0813443
效率差多少?在這裡時間還不到1%,代碼少寫一個if字句
看到有文章說這種方法在大量更新ui時會引發大量異常,導致性能下降
我測試了一下,耗時和循環次數是很平穩的線性關系,而且也沒有發現幾個Exception相關性能計數器有問題,這說明這又是某老外朋友想當然的說法。
2、CheckForIllegalCrossThreadCalls在.net1.x中默認是false,也就是不檢查,.Net2.0和3.x默認是true
說明這是ms有意的引導,說不定以後不讓你改了。這也是很多1.x用戶在剛用2.0時不習慣跨線程更新ui的原因之一。
3、死穴:安全性
CheckForIllegalCrossThreadCalls容許子線呈隨時更新ui,在同一個test函數體內,不能保證自身事務的一致性。給label1付了值
一回頭,就已經被別人改了,這和超市的踩踏事件的後果一樣嚴重。
當然你可以自己加鎖,用信號量,這樣還不如直接使用Invoke了,你只是又把別人做好的事情做了一遍。
如果你覺的你的應用不會考慮在寫入ui的同時來讀取ui,而傾向使用CheckForIllegalCrossThreadCalls來追求效率的話,也是不恰當的做法。
首先CheckForIllegalCrossThreadCalls並不能讓效率發生本質的變化。
其次需求永遠是變化的,現在不考慮不等於以後不會碰到
聽從ms的引導。否則以後要在高版本的.Net framework中移植代碼的時候需要花費數倍的人工。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication34
...{
public partial class Form1 : Form
...{
public Form1()
...{
InitializeComponent();
}
//模擬一個實際應用
//對label1付值後立馬檢查他的值,如果已經被改過則拋出異常
void test()
...{
string strText = Guid.NewGuid().ToString();
this.label1.Text = strText;
if (this.label1.Text != strText)
...{
//測試性能時把這一句注釋掉!!!!!!!!!!!!!!!!!!!!!!1
throw new Exception("label1的值意外改變");
}
}
//不使用invoke方法直接進入Control
void doThread()
...{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Reset();
sw.Start();
for (int i = 0; i < 100; i++)
...{
test();
System.Threading.Thread.Sleep(10);
}
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
//使用invoke方法
public delegate void dTest();
void invokeThread()
...{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Reset();
sw.Start();
for (int i = 0; i < 100; i++)
...{
if (this.InvokeRequired)
...{
this.Invoke(new dTest(test));
}
else
...{
test();
}
System.Threading.Thread.Sleep(10);
}
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
//通過CheckForIllegalCrossThreadCalls的值忽略跨線程錯誤,這時會拋出異常
private void button1_Click(object sender, EventArgs e)
...{
Form1.CheckForIllegalCrossThreadCalls = false;
new System.Threading.Thread(new System.Threading.ThreadStart(doThread)).Start();
new System.Threading.Thread(new System.Threading.ThreadStart(doThread)).Start();
}
//通過invoke方法,在主線程排隊,lable1的值在test函數體內永遠是線程安全的.
private void button2_Click(object sender, EventArgs e)
...{
new System.Threading.Thread(new System.Threading.ThreadStart(invokeThread)).Start();
new System.Threading.Thread(new System.Threading.ThreadStart(invokeThread)).Start();
}
}
}