C#多線程函數如何傳參數和返回值

 

提起多線程,不得不提起 委託(delegates)這個概念.

我理解的委託就是 具有 同樣參數和返回值 的函數的集合.
比如
public delegate void MyDelegate(int arg);
就是這種形式的函數 void Myfuntion(int i); 的集合.
如何將一個函數加入 委託 的集合?
MyDelegate dele = new MyDelegate(Myfuntion1);
再增加一個
dele += new MyDelegate(Myfuntion2);
...
委託函數 dele 就是 具有整數參數和空返回值的函數 Myfuntion1,2的集合.
調用這個委託函數
dele(1);
就是逐個調用 Myfuntion1,2,...

一般線程函數的聲明和啓動

Thread t = new Thread(new ThreadStart(MyFunction));
t.Start();
正是調用了沒有參數和返回值的 委託函數 ThreadStart
其中的參數MyFunction 是 這個委託函數中的一員.

很明顯 這樣無法傳參數和返回值,那我們該怎麼辦?

答案就在委託 的BeginInvoke() 方法上, BeginInvoke() 也是(異步)啓動一個新線程.
例如
MyDelegate dele = new MyDelegate (MyFunction);
dele.BeginInvoke(10,"abcd");
void MyFunction(int count, string str);
可以實現參數的傳遞.

如何收集線程函數 的 返回值?

BeginInvoke 對應 有個 EndInvoke 方法,而且運行完畢返回 IAsyncResult 類型的返回值.
這樣我們可以這樣收集 線程函數的 返回值

MyDelegate dele = new MyDelegate (MyFunction);
IAsyncResult ref = dele.BeginInvoke(10,"abcd");
...
int result = dele.EndInvoke(ref); <----收集 返回值
int MyFunction(int count, string str); <----帶參數和返回值的 線程函數


提示:"線程間操作無效:從不是創建控件“XX”的線程訪問它"

一般來說,直接在子線程中對窗體上的控件操作是會出現異常,這是由於子線程和運行窗體的線程是不同的空間,因此想要在子線程來操作窗體上的控件,是不可能簡單的通過控件對象名來操作,但不是說不能進行操作,微軟提供了Invoke的方法,其作用就是讓子線程告訴窗體線程來完成相應的控件操作。 現在用一個用線程控制的進程條來說明,大致的步驟如下:

1. 創建Invoke函數,大致如下: /// /// Delegate function to be invoked by main thread ///

private void InvokeFun()

{ if( prgBar.Value < 100 )

prgBar.Value = prgBar.Value + 1; }

2. 子線程入口函數: /// /// Thread function interface ///

private void ThreadFun()

{ //Create invoke method by specific function

MethodInvoker mi = new MethodInvoker( this.InvokeFun );

for( int i = 0; i < 100; i++ ) { this.BeginInvoke( mi ); Thread.Sleep( 100 ); } }

3. 創建子線程:

Thread thdProcess = new Thread( new ThreadStart( ThreadFun ) );

thdProcess.Start();


出現這個問題主要是因爲在線程方法中操作了界面上的控件..lstPrime.Items.Add()

可以這樣改下..

//定義一個委託
public delegate void MyInvoke(string str);

//定義一個操作界面的方法
private void UpdateUI(string str)
{
   //增加項
   this.lstPrime.Items.Add(str);
}


//在線程的方法中,即你的Generate方法..
//裏面只要是涉及到Items.Add操作的都改成如下形式即可..
//比如lstPrime.Items.Add(2);改成:
MyInvoke mi=new MyInvoke(UpdateUI);
this.BeginInvoke(mi,new object[]{ "2 "});



============================================================================



BeginInvoke與Invoke的含義[轉載]BeginInvoke 方法真的是新開一個線程進行異步調用嗎?

參考以下代碼:

public delegate void treeinvoke();
private void UpdateTreeView()
{
MessageBox.Show(System.Threading.Thread.CurrentThread.Name);
}
private void button1_Click(object sender, System.EventArgs e)
{
System.Threading.Thread.CurrentThread.Name = "UIThread";
treeView1.BeginInvoke(new treeinvoke(UpdateTreeView));
}
看看運行結果,彈出的對話框中顯示的是 UIThread,這說明 BeginInvoke 所調用的委託根本就是在 UI 線程中執行的。

既然是在 UI 線程中執行,又何來“異步執行”一說呢?

我們再看看下面的代碼:

public delegate void treeinvoke();
private void UpdateTreeView()
{
MessageBox.Show(Thread.CurrentThread.Name);
}
private void button1_Click(object sender, System.EventArgs e)
{
Thread.CurrentThread.Name = "UIThread";
Thread th = new Thread(new ThreadStart(StartThread));
th.Start();
}
private void StartThread()
{
Thread.CurrentThread.Name = "Work Thread";
treeView1.BeginInvoke(new treeinvoke(UpdateTreeView));
}
再看看運行結果,彈出的對話框中顯示的還是 UIThread,這說明什麼?這說明 BeginInvoke 方法所調用的委託無論如何都是在 UI 線程中執行的。

那 BeginInvoke 究竟有什麼用呢?

在多線程編程中,我們經常要在工作線程中去更新界面顯示,而在多線程中直接調用界面控件的方法是錯誤的做法,具體的原因可以在看完我的這篇之後看看這篇:在多線程中如何調用Winform,如果你是大牛的話就不要看我這篇了,直接看那篇吧,反正那篇文章我沒怎麼看懂。

Invoke 和 BeginInvoke 就是爲了解決這個問題而出現的,使你在多線程中安全的更新界面顯示。

正確的做法是將工作線程中涉及更新界面的代碼封裝爲一個方法,通過 Invoke 或者 BeginInvoke 去調用,兩者的區別就是一個導致工作線程等待,而另外一個則不會。

而所謂的“一面響應操作,一面添加節點”永遠只能是相對的,使 UI 線程的負擔不至於太大而以,因爲界面的正確更新始終要通過 UI 線程去做,我們要做的事情是在工作線程中包攬大部分的運算,而將對純粹的界面更新放到 UI 線程中去做,這樣也就達到了減輕 UI 線程負擔的目的了。

而在那段更新樹節點的代碼中,其實唯一起作用的代碼是:System.Threading.Thread.Sleep(100);,它使 UI 線程有了處理界面消息的機會,其實 數碼幽靈 將問題複雜化了,只要以下的代碼就可以很好的工作了。

private void button1_Click_(object sender, System.EventArgs e)
{
TreeNode tn;
for(int i=0;i<100000;i++)
{
tn=new TreeNode (i.ToString());
this.treeView1.Nodes[0].Nodes.Add(tn);
if (i%100 == 0) Application.DoEvents();
}

發佈了44 篇原創文章 · 獲贊 8 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章