大概有4種方法:
Dispatcher、非同步委託、手動多線程、BackgroundWorker,另外還有一個DispatcherTimer,是定時器。
其中Dispatcher與DispatcherTimer相同,是利用在主線程進行任務優先順序的排列來類比多線程,因此其中實質是單線程
,所以大負荷的運算不宜用它們。
BackgroundWorker會到線程池去抓一個線程作為背景工作執行緒,完成工作後切換回 UI 線程調用你定義的處理完成工作的委
托。每計算出一次值,就會用worker.ReportProgress(percentComplete)通知回呼函數,並可在回呼函數中更新UI。特
別適合需要更新已完成的百分比的情境。
這些都可以和資料繫結結合起來。
①非同步委託還是手動多線程
非同步委託:支援線程結束時回調(BeginInvoke方法的的第一個參數),但不支援在外部中斷線程,多用於更新UI。
手動線程:支援在外部中斷線程(Thread.Abort方法),但不易得到函數的傳回值。另外還不能更新UI。因為“Windows
表單”使用單一執行緒 Apartment (STA) 模型(WPF表單也一樣),STA模型意味著可以在任何線程上建立視窗,但視窗一旦建立後
就不能切換線程,並且對它的所有函數調用都必須在其建立線程上發生。特別是在註冊事件的回呼函數中要小心。
委託的用法:
public class MyDelegateTest<br />{<br /> // 步驟1,聲明delegate對象<br /> public delegate bool MyDelegate(string name);</p><p> // 這是我們欲傳遞的方法,它與MyDelegate具有相同的參數和傳回值類型<br /> public static bool MyDelegateFunc(string name)<br /> {<br /> Console.WriteLine("Hello, {0}", name);<br />return ture;<br /> }</p><p> public static voidMain ()<br /> {<br /> // 步驟2,建立delegate對象<br /> MyDelegate md = new MyDelegate(MyDelegateTest.MyDelegateFunc);</p><p> // 步驟3,調用delegate<br /> Console.WriteLine("Result is {0}", md("sam1111"));<br /> }<br />}<br />
這種調用方法和用委派物件的Invoke一樣,都是同步調用,會阻塞當前線程。非同步呼叫需要用BeginInvoke:
class Program<br />{<br />private static int newTask(int ms)<br />{<br />Console.WriteLine("任務開始");<br />Thread.Sleep(ms);<br />Random random = new Random();<br />int n = random.Next(10000);<br />Console.WriteLine("任務完成");<br />return n;<br />} </p><p>private delegate int NewTaskDelegate(int ms);<br />static void Main(string[] args)<br />{<br />NewTaskDelegate task = newTask;<br />IAsyncResult asyncResult = task.BeginInvoke(2000, null, null); // EndInvoke方法將被阻塞2</p><p>秒<br />//Do Something else<br />int result = task.EndInvoke(asyncResult);<br />Console.WriteLine(result);<br />}<br />}<br />
這裡的BeginInvoke就是非同步呼叫,運行後立即返回,不會引起當前線程的阻塞。但是在本常式中,因為newTask中要
Sleep 2秒中,如果Do Something else的時間沒有2秒的話,EndInvoke還是會引起當前線程的阻塞,因為它要等待
newTask執行完畢。
那能不能不調用EndInvoke,讓它自己結束呢?不太好。因為一來BeginInvoke和EndInvoke必須成對調用。即使不需要返
回值,但EndInvoke還是必須調用,否則可能會造成記憶體流失,因為它是利用了線程池資源。二來往往要調用EndInvoke
來獲得函數的傳回值。
如果是用BeginInvoke來進行輪詢操作,EndInvoke是無法返回的,這時可以用一個變數來控制一下:
在我的應用程式情境裡是定義了一個封裝類:
class IsNetworkAvailableDelegate<br /> {<br /> private IsNetworkAvailableWrapper _available;<br /> private delegate void MyDelegate();<br /> private MyDelegate _dele;<br /> private IAsyncResult _result;<br /> private bool _running = true;</p><p> private void TryConnect()<br /> {<br /> while (this._running)<br /> {<br /> try<br /> {<br /> Ping _ping = new Ping();<br /> if (_ping.Send("www.baidu.com").Status == IPStatus.Success)<br /> this._available.IsNetworkAvailable = true;<br /> else<br /> this._available.IsNetworkAvailable = false;</p><p> }<br /> catch<br /> {<br /> this._available.IsNetworkAvailable = false;<br /> }<br /> Thread.Sleep(500);<br /> }<br /> }<br /> public IsNetworkAvailableDelegate(IsNetworkAvailableWrapper available)<br /> {<br /> this._available = available;<br /> this._dele = new MyDelegate(this.TryConnect);<br /> this._result = this._dele.BeginInvoke(null, null);<br /> }</p><p> public void EndInvoke()<br /> {<br /> this._running = false;<br /> this._dele.EndInvoke(this._result);<br /> }<br /> }</p><p>
要想完全與當前線程非同步,可以利用BeginInvoke的第二個參數,設定一個函數執行完成後的回呼函數:
private delegate int MyDelegate(int a);<br />private int method(int a)<br />{<br /> Thread.Sleep(10000);<br /> Console.WriteLine(a);<br /> return 100;<br />}<br />private void MethodCompleted(IAsyncResult asyncResult)<br />{<br /> if (asyncResult == null) return;<br /> textBox1.Text = (asyncResult.AsyncState as MyDelegate).EndInvoke(asyncResult).ToString();<br />}<br />private void button1_Click(object sender, EventArgs e)<br />{<br /> MyDelegate my = method;<br /> IAsyncResult asyncResult = my.BeginInvoke(MethodCompleted, my);<br />}<br />
這樣就可以讓當前線程完全沒有等待的感覺了。
不過有時候,當前線程又想要利用函數執行的時間幹點私活,然後在函數執行完成後再做別的,可以用WaitOne方法:
class Program<br /> {<br /> public delegate int BinaryOpDelegate(int x, int y);<br /> static void Main(string[] args)<br /> {<br /> Console.WriteLine("***** Async Delegate Invocation *****");</p><p> // Print out the ID of the executing thread.<br /> Console.WriteLine("Main() invoked on thread {0}.",<br /> Thread.CurrentThread.ManagedThreadId); </p><p> // Invoke Add() on a secondary thread.<br /> BinaryOpDelegate b = new BinaryOpDelegate(Add);<br /> IAsyncResult iftAR = b.BeginInvoke(10, 10, null, null);</p><p> // This message will keep printing until<br /> // the Add() method is finished.<br /> //用WaitOne 等待非同步委託執行完成<br /> while (!iftAR.AsyncWaitHandle.WaitOne(1000, true))<br /> {<br /> Console.WriteLine("Doing more work in Main()!");<br /> }</p><p> // Obtain the result of the Add()<br /> // method when ready.<br /> int answer = b.EndInvoke(iftAR);<br /> Console.WriteLine("10 + 10 is {0}.", answer);<br /> Console.ReadLine();<br /> }</p><p> #region Very slow addition...<br /> static int Add(int x, int y)<br /> {<br /> // Print out the ID of the executing thread.<br /> Console.WriteLine("Add() invoked on thread {0}.",<br /> Thread.CurrentThread.ManagedThreadId);<br />//類比一個花費時間較長的行為<br /> // Pause to simulate a lengthy operation.<br /> Thread.Sleep(5000);<br /> return x + y;<br /> }<br /> #endregion<br /> }</p><p>
(PS:用lambda文法還可以寫出很炫的句子:)
Action<object> action=(obj)=>method(obj);<br />action.BeginInvoke(obj,ar=>action.EndInvoke(ar),null);<br />
其實還可以更直接:
new Action<object>((obj) => method(obj)).BeginInvoke(ar=>action.EndInvoke(ar), null);<br /> )
Invoke方法的主要功能就是協助你在UI線程上調用委託所指定的方法。Invoke方法首先檢查發出調用的線程(即當前線程
)是不是UI線程,如果是,直接執行委託指向的方法,如果不是,它將切換到UI線程,然後執行委託指向的方法。
手動線程的調用:
典型的寫法是:
public class ProcClass<br />{<br /> private string procParameter = "";<br /> private string result = "";<br /> public ProcClass(string parameter)<br /> {<br /> procParameter = parameter;<br /> }<br /> public void ThreadProc()<br /> {<br /> }<br />}<br />ProcClass threadProc = new ProcClass("use thread class");<br />Thread thread = new Thread( new ThreadStart( threadProc.ThreadProc ) );<br />thread.IsBackground = true;<br />thread.Start();<br />
因為Thread調用的函數只能是無參數且無傳回值的,因此通常要用類進行封裝。
如果線程既需要可中斷,又要與UI打交道:
public class ProcClass<br />{<br /> private string procParameter = "";<br /> private Form1.OutDelegate delg = null;<br /> public ProcClass(string parameter, Form1.OutDelegate delg)<br /> {<br /> procParameter = parameter;<br /> this.delg = delg;<br /> }<br /> public void ThreadProc()<br /> {<br /> delg.BeginInvoke("use ProcClass.ThreadProc()", null, null);<br /> }<br />}<br />ProcClass threadProc = new ProcClass("use thread class", new OutDelegate(OutText));<br />Thread thread = new Thread( new ThreadStart( threadProc.ThreadProc ) );<br />thread.IsBackground = true;<br />thread.Start();<br />