C#中Invoke和BeginInvoke實(shí)際應(yīng)用詳解
前言
最近,在研究Invoke的使用,但是真的是一頭霧水,網(wǎng)上看了很多資料,感覺(jué)還是看不懂,因?yàn)閷?duì)于入門級(jí)的小白,想像不出Invoke的應(yīng)用場(chǎng)景,更談不上如何用了?
1、Invoke到底是什么?
Invoke的本質(zhì)只是一個(gè)方法,方法一定是要通過(guò)對(duì)象來(lái)調(diào)用的。
一般來(lái)說(shuō),Invoke其實(shí)用法只有兩種情況:
- Control的Invoke
- Delegate的Invoke
也就是說(shuō),Invoke前面要么是一個(gè)控件,要么是一個(gè)委托對(duì)象。
2、什么時(shí)候用Invoke
2.1 Control的Invoke
Control的Invoke一般用于解決跨線程訪問(wèn)的問(wèn)題,比如你想操作一個(gè)按鈕button,你就要用button.Invoke,你想操作一個(gè)文本label,你就要用label.Invoke,但是大家會(huì)發(fā)現(xiàn)很麻煩,如果我想既操作button,又操作label,能不能寫(xiě)在一起呢?當(dāng)然可以。
我們知道,主窗體是一個(gè)Form,F(xiàn)orm自然也是繼承Control的,所以Form也有Invoke的方法,可以直接調(diào)用Form.Invoke,這就是我們常見(jiàn)的this.Invoke。這就是為什么有的Invoke前面啥都沒(méi)有的問(wèn)題,其實(shí)前面是this,只不過(guò)省略了。
2.2 Delegate的Invoke
Delegate的Invoke其實(shí)就是從線程池中調(diào)用委托方法執(zhí)行,Invoke是同步的方式,會(huì)卡住調(diào)用它的UI線程。很抽象吧。
3、實(shí)驗(yàn)
我們來(lái)做個(gè)簡(jiǎn)單的實(shí)驗(yàn)。
3.1 新建一個(gè)From ,來(lái)個(gè)Button,我想實(shí)現(xiàn)的功能是,點(diǎn)擊Button時(shí), Button 變成Disable,并開(kāi)始顯示計(jì)算1到8(每隔1秒加1),加到8后,跳出循環(huán),然后把Button Enable,很簡(jiǎn)單吧。

小事情拉,于是開(kāi)始行動(dòng),很快就搞定了,代碼如下:
namespace InvokeTest1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// Butten 點(diǎn)擊事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAddFunction_Click_1(object sender, EventArgs e)
{
btnAddFunction.Enabled = false;
for (int i = 1; i < 8; i++)
{
btnAddFunction.Text = i.ToString();
Thread.Sleep(1000);
}
btnAddFunction.Text = "點(diǎn)擊開(kāi)始運(yùn)行";
btnAddFunction.Enabled = true;
}
}
}開(kāi)始運(yùn)行,點(diǎn)擊Button,控件是變成Disable,但是沒(méi)有實(shí)現(xiàn)計(jì)數(shù)呀,而是一直就這樣停止8秒,就像被卡住了一樣。

8秒后,Button控件直接變成Enable。沒(méi)有1---8出現(xiàn),邏輯不對(duì)啊,怎么回事?????
原因:直接主線程休眠是達(dá)不到效果的,此時(shí)桌面還處于假死狀態(tài),更新不了text值。代碼放在了UI線程執(zhí)行,阻塞了UI的顯示,所以中間的結(jié)果你看不到。
3.2 找到了原因,那就好辦了,既然代碼放在了UI線程,那就新建個(gè)線程,在那里面更新UI控件好了。信心滿滿的開(kāi)始行動(dòng)。
namespace InvokeTest1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// Butten 點(diǎn)擊事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAddFunction_Click_1(object sender, EventArgs e)
{
//啟動(dòng)一個(gè)線程,在這個(gè)線程里更新Button值
new Thread(ThreadTask).Start();
}
/// <summary>
/// 線程函數(shù)
/// </summary>
public void ThreadTask()
{
btnAddFunction.Enabled = false;
for (int i = 1; i < 8; i++)
{
btnAddFunction.Text = i.ToString();
Thread.Sleep(1000);
}
btnAddFunction.Text = "點(diǎn)擊開(kāi)始運(yùn)行";
btnAddFunction.Enabled = true;
}
}
}
真實(shí)現(xiàn)了數(shù)字的更新。

功能是實(shí)現(xiàn)了,但沒(méi)有達(dá)到我的目的,主角都沒(méi)有登場(chǎng),就謝幕了啊。網(wǎng)上查看了資料,說(shuō),這種方法,是不穩(wěn)定的,特別是主窗口控件比較多的時(shí)候,很容易出錯(cuò),造成畫(huà)面混亂。為什么呢?因?yàn)榭丶窃谥骶€程中創(chuàng)建的(比如this.Controls.Add(...);),進(jìn)入控件的事件響應(yīng)函數(shù)時(shí),是在控件所在的線程,并不是主線程。在控件的事件響應(yīng)函數(shù)中改變控件的狀態(tài),可能與主線程發(fā)生線程沖突。如果主線程正在重繪控件外觀,此時(shí)在別的線程改變控件外觀,就會(huì)造成畫(huà)面混亂。
4、主角出場(chǎng)
4.1 C#的委托機(jī)制,一般有下面幾種方式。
//第一種
btnAddFunction.Invoke(new EventHandler(delegate{button1.Text = "關(guān)閉";}));
//第二種
this.Invoke(new EventHandler(delegate{button1.Text = "關(guān)閉";}));
//第三種 網(wǎng)上說(shuō)自C# 3.0開(kāi)始就有了
this.Invoke(new Action(() =>{ button1.Text = "關(guān)閉";}));現(xiàn)在應(yīng)用最多就是第3種了,因?yàn)楝F(xiàn)在版本基本上都是4.0以上了,所以線程函數(shù)改為如下的:
namespace InvokeTest1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// Butten 點(diǎn)擊事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAddFunction_Click_1(object sender, EventArgs e)
{
//啟動(dòng)一個(gè)線程,在這個(gè)線程里更新Button值
new Thread(ThreadTask).Start();
}
/// <summary>
/// 線程函數(shù)
/// </summary>
public void ThreadTask()
{
//首先將button對(duì)象禁用
this.Invoke(new Action(() =>
{
btnAddFunction.Enabled = false;
}));
for (int i = 0; i < 10; i++)
{
this.Invoke(new Action(() =>
{
btnAddFunction.Text = i.ToString();
}));
Thread.Sleep(1000);
}
//雖然不是循環(huán)內(nèi),請(qǐng)不要忘記,你的調(diào)用依然在輔助線程中,所以,還是需要invoke的。
this.Invoke(new Action(() =>
{
btnAddFunction.Text = "點(diǎn)擊開(kāi)始運(yùn)行";
btnAddFunction.Enabled = true;
}));
}
}
}
4.2 Control的Invoke標(biāo)準(zhǔn)用法
其實(shí),對(duì)于Control的Invoke,更標(biāo)準(zhǔn)的用法是先加判斷,再調(diào)用。
if (this.lbl_Value.InvokeRequired)
{
this.lbl_Value.Invoke(new Action(() =>
{
this.lbl_Value.Text = "Invoke功能測(cè)試";
}));
}
else
{
this.lbl_Value.Text = "Invoke測(cè)試失效";
}
InvokeRequired是Control的一個(gè)屬性,官方解釋為:獲取一個(gè)值,該值指示調(diào)用方在對(duì)控件進(jìn)行方法調(diào)用時(shí)是否必須調(diào)用 Invoke 方法,因?yàn)檎{(diào)用方位于創(chuàng)建控件所在的線程以外的線程中。如果控件的 Handle 是在與調(diào)用線程不同的線程上創(chuàng)建的(說(shuō)明您必須通過(guò) Invoke 方法對(duì)控件進(jìn)行調(diào)用),則為 true;否則為 false。
簡(jiǎn)單來(lái)說(shuō),就是如果通過(guò)多線程去操作這個(gè)控件,那么這個(gè)屬性則為True,否則為False。
實(shí)例化一個(gè) this invoke的用法
?
using System.Threading;
public delegate void MyInvoke(string str);//invoke方法創(chuàng)建委托
private void btnStartThread_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWord));
thread.Start();
}
public void DoWord()
{
MyInvoke mi = new MyInvoke(SetTxt);//實(shí)例化一個(gè)委托,并且指定委托方法
BeginInvoke(mi,new object[]{"abc"}); //調(diào)用invoke方法
}
public void SetTxt(string str)//委托對(duì)應(yīng)的方法
{
txtReceive.Text += str;
}
?4.3 Delegate的Invoke 標(biāo)準(zhǔn)寫(xiě)法
對(duì)于Delegate的Invoke,我們一般判斷這個(gè)方法之前,也是做個(gè)判斷,判斷這個(gè)委托對(duì)象是否為Null,所以更標(biāo)準(zhǔn)的寫(xiě)法如下:
DelegateInvokeFun testDelegate = new DelegateInvokeFun(DelegateInvokeMethod); testDelegate?.Invoke();
5、 Invoke和BeginInvoke
Control.Invoke 和 Control.BeginInvoke
5.1 測(cè)試實(shí)例1 利用 控件中的Invoke 和 BeginInvoke 方法
作用1:在線程中執(zhí)行訪問(wèn)和修改UI內(nèi)容
作用2:Invoke可以阻塞線程,等待UI操作返回
作用3:BeginInvoke不阻塞線程,后臺(tái)刷新UI,提高程序的流暢性
public partial class frmMain : Form
{
public frmMain ()
{
InitializeComponent();
}
public void ThreadRun()
{
while (true)
{
Thread.Sleep(1);
this.Invoke(new Action(() =>
{
MessageBox.Show("Invoke的方法");
// 在該this(Form)控件的線程中執(zhí)行Action中的委托
// 你可以在此獲取UI變量 或者 改變UI變量
// 但是 ThreadRun線程會(huì)被阻塞 等待 Action 執(zhí)行完成
}));
this.BeginInvoke(new Action(() =>
{
MessageBox.Show("BeginInvoke的方法");
// 在該this(Form)控件的線程中執(zhí)行Action中的委托
// 你可以在此獲取UI變量 或者 改變UI變量
// 但是 ThreadRun線程不會(huì)被阻塞繼續(xù)向下執(zhí)行
}));
}
}
}5.2 測(cè)試實(shí)例2 Action 等delegate 中Invoke和BeginInvoke的作用
作用1.Invoke在當(dāng)前函數(shù)中立即執(zhí)行,相當(dāng)于直接調(diào)用該Action所注冊(cè)的所有函數(shù),阻塞當(dāng)前函數(shù)幀
作用2.BeginInvoke在當(dāng)前函數(shù)幀中開(kāi)辟這個(gè)線程去執(zhí)行Action所注冊(cè)的函數(shù),不阻塞當(dāng)前函數(shù)幀
作用3.BeginInvoke 中有2額外兩個(gè)參數(shù) (arg1 回調(diào)委托,任意參數(shù))用于beginInvoke完成后,執(zhí)行該某些動(dòng)作,
注意:結(jié)束完回調(diào)的函數(shù) 所在線程 為調(diào)用beginInvoke 的線程
private void Form1_Load(object sender, EventArgs e)
{
// 在當(dāng)前函數(shù)所在線程中執(zhí)行,當(dāng)前函數(shù)線程阻塞
ShowMsg += (str) => { Debug.WriteLine(str); };
ShowMsg.Invoke("This is Invoke test!");
// 開(kāi)辟一個(gè)線程執(zhí)行 BeginInvoke Test,當(dāng)前函數(shù)線程不阻塞
ShowMsg.BeginInvoke(" BeginInvoke Test", null, null);
// AsyncCallback 回調(diào)委托
// IAsyncResult 異步執(zhí)行的結(jié)果,可以自我繼承,添加自定義參數(shù) 用于顯示異步執(zhí)行的狀態(tài)
// index(object類型) 外部傳參,存儲(chǔ)在IAsyncResult.AsyncState中
AsyncCallback asyncCallback = ar => { Debug.WriteLine($"beginInvoke 執(zhí)行完成 回調(diào){ar.AsyncState}"); };
int index = 0;
ShowMsg.BeginInvoke(" BeginInvoke Test", asyncCallback, index);
}6 應(yīng)用 測(cè)速下載多個(gè)文件時(shí)耗用的時(shí)間 - 異步編程實(shí)現(xiàn)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace InvokeDemo
{
public partial class 異步編程 : Form
{
public delegate string delegateObj(string paht);
public 異步編程()
{
InitializeComponent();
delobj = new delegateObj(copyFile);
}
delegateObj delobj=null;
/// <summary>
/// 同步拷貝文件,并輸出文件名稱
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
String[] paths = Directory.GetFiles(@"F:\迅雷下載");
for (int i = 0; i < paths.Length; i++)
{
this.listBox1.Items.Add(copyFile(paths[i]));
}
}
/// <summary>異步拷貝文件,并輸出文件名
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
String[] paths = Directory.GetFiles(@"F:\迅雷下載");
for (int i = 0; i < paths.Length; i++)
{
delobj.BeginInvoke(paths[i], callback, Path.GetFileName(paths[i]));//最后一個(gè)參數(shù)是回調(diào)狀態(tài) ,如果不需要回調(diào)函數(shù)的話,直接null,這樣就不需要調(diào)用EndInvoke,單純的BeginInvoke即可
}
}
public void callback(IAsyncResult result)
{
string filename = "";
//if (this.listBox1.InvokeRequired)
//{
// filename = this.listBox1.EndInvoke(result).ToString();
// this.listBox1.Items.Add(filename + "復(fù)制成功");
// this.listBox2.Items.Add(result.AsyncState.ToString() + "復(fù)制成功");
//}
filename = delobj.EndInvoke(result);
//this.listBox1.Items.Add(filename + "復(fù)制成功");
//this.listBox2.Items.Add(result.AsyncState.ToString() + "復(fù)制成功");
Console.WriteLine(result.AsyncState.ToString() + "復(fù)制成功");
}
/// <summary>
/// 拷貝文件用
/// </summary>
/// <param name="filename"></param>
public string copyFile(string filename)
{
if (filename.EndsWith("desktop.ini"))
return "desktop.ini";
if (File.Exists(@"F:\迅雷下載2\" + Path.GetFileName(filename)))
{
File.Delete(@"F:\迅雷下載2\" + Path.GetFileName(filename));
}
File.Copy(filename, @"F:\迅雷下載2\" + Path.GetFileName(filename));
return Path.GetFileName(filename);
}
}
}總結(jié)
到此這篇關(guān)于C#中Invoke和BeginInvoke實(shí)際應(yīng)用詳解的文章就介紹到這了,更多相關(guān)C# Invoke和BeginInvoke應(yīng)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C# Winform實(shí)現(xiàn)截圖工具的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何使用C# Winform制作一個(gè)簡(jiǎn)單的截圖工具,從而實(shí)現(xiàn)截圖功能,文中的示例代碼講解詳細(xì),有需要的可以參考下2024-02-02
WPF實(shí)現(xiàn)授權(quán)碼顯示密文并支持換行
這篇文章主要為大家詳細(xì)介紹了如何使用WPF實(shí)現(xiàn)授權(quán)碼顯示密文并支持換行,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2024-10-10
C#表達(dá)式中的動(dòng)態(tài)查詢?cè)斀狻咀g】
這篇文章主要給大家介紹了關(guān)于C#表達(dá)式中動(dòng)態(tài)查詢的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
C# Winform 實(shí)現(xiàn)TCP發(fā)消息
這篇文章主要介紹了C# Winform 實(shí)現(xiàn)TCP發(fā)消息的示例,幫助大家更好的理解和學(xué)習(xí)使用c#技術(shù),感興趣的朋友可以了解下2021-03-03

