C#並行開發_Thread/ThreadPool, Task/TaskFactory, Parallel

轉載自http://blog.csdn.net/razorluo/article/details/7821344


大家好,本次討論的是C#中的並行開發,給力吧,隨着並行的概念深入,哥也趕上這個潮流了,其實之前討論C#的異步調用或者C#中BeginInvoke或者Invoke都已經涉及了部分本篇的內容。

參考書目:Professional.C#.4.0.and.NET.4.pdf 以及 Pro .NET 4 Parallel Programming in C#.pdf

Parallel Program in C#中有Delegate的Asynchronous也有Thread的Asynchronous,前者已經在《C#異步調用詳細》中闡述清楚了,那它跟Thread的有什麼區別呢?

可能大家都混淆了,我也快糊塗了,C#中異步(並行)編程有幾類:

1. Asynchronous Delegates
        Asychronous calling is used when you have work items that should be handled in the background and you care when they finish.

2. BackgroundWorker
        Use BackgroundWorker if you have a single task that runs in the background and needs to interact with the UI. and use it if you don't care when they finish their task. The task of marshalling data and method calls to the UI thread are handled automatically through its event-based model.
        Avoid BackgroundWorker if (1) your assembly does not already reference the System.Windows.Form assembly, (2) you need the thread to be a foreground thread, or (3) you need to manipulate the thread priority.

3. ThreadPool

        Use a ThreadPool thread when efficiency is desired. The ThreadPool helps avoid the overhead associated with creating, starting, and stopping threads.
        Avoid using the ThreadPool if (1) the task runs for the lifetime of your application, (2) you need the thread to be a foreground thread, (3) you need to manipulate the thread priority, or (4) you need the thread to have a fixed identity (aborting, suspending, discovering).

4. Thread class
        Use the Thread class for long-running tasks and when you require features offered by a formal threading model, e.g., choosing between foreground and background threads, tweaking the thread priority, fine-grained control over thread execution, etc.

5. Task Parallel Library 
        Task/TaskFactory, Parallel.For, Parallel.ForEach, Parallel.Invoke

6. Parallel LINQ

       TBD

好了進入主題了,先來介紹Thread命名空間。

Thread class

創建與指定委託(Create):
can be constructed with two kinds of delegate:
1. ThreadStart: void ()(void)
2. ParameterizedThreadStart: void ()(Object obj)
跟Asynchronous delegate 相比,輸入參數已經很受限制,才支持一個,而且還是object對象的,沒有進行類型檢查。

控制:
Start() or Start(obj)
Start是直接讓新創建的異步執行,類似於Asynchronous的BeginInvoke方法,就是異步於caller來執行。

下面的代碼分別顯示Asynchronous Delegate以及Thread做同樣的事情
Asynchronous Delegate部分

[csharp] view plaincopy
  1. public void AsyncOperation(int data)  
  2. {  
  3.     int i = 0;  
  4.     while (i++ < data)  
  5.     {  
  6.         Thread.Sleep(1000);  
  7.         Console.WriteLine(string.Format("Running for {0} seconds, in thread id: {1}.", i, Thread.CurrentThread.ManagedThreadId));  
  8.     }  
  9. }  
  10. public delegate void AsyncOperationDelegate(int data);  
  11. public void RunBeginInvoke()  
  12. {  
  13.     AsyncOperationDelegate d1 = new AsyncOperationDelegate(AsyncOperation);  
  14.     d1.BeginInvoke(3, nullnull);  
  15.     int i = 0;  
  16.     while (i++ < 3)  
  17.     {  
  18.         Thread.Sleep(1000);  
  19.         Console.WriteLine(string.Format("[BeginInvoke]Running for {0} seconds, in thread id: {1}.", i, Thread.CurrentThread.ManagedThreadId));  
  20.     }  
  21. }  

Thread部分:
[csharp] view plaincopy
  1. private void AsyncOperation(object obj)  
  2. {  
  3.     int data = (int)obj;  
  4.     int i = 0;  
  5.     while (i++ < data)  
  6.     {  
  7.         Thread.Sleep(1000);  
  8.         Console.WriteLine(string.Format("Running for {0} seconds, in thread id: {1}.", i, Thread.CurrentThread.ManagedThreadId));  
  9.     }  
  10. }  
  11. public void RunThread()  
  12. {  
  13.     Thread t1 = new Thread(new ParameterizedThreadStart(AsyncOperation));  
  14.     t1.Start((object)3);  
  15.     int i = 0;  
  16.     while (i++ < 3)  
  17.     {  
  18.         Thread.Sleep(1000);  
  19.         Console.WriteLine(string.Format("[Thread]Running for {0} seconds, in thread id: {1}.", i, Thread.CurrentThread.ManagedThreadId));  
  20.     }  
  21. }  

使用起來比Asynchronous Delegate實在彆扭以及難看。
書中提議使用類的成員函數作爲委託函數,同時由於類成員函數能否訪問類的成員變量,從而實現複雜或者多個參數傳遞,或者獲取修改後的值。具體例子如下:

[csharp] view plaincopy
  1. class ThreadData  
  2. {  
  3.     public int data = 0;  
  4.     public ThreadData()  
  5.     {   
  6.       
  7.     }  
  8.   
  9.     public void RunThread()  
  10.     {   
  11.         int i = 0;  
  12.         while (i++ < 1000000)  
  13.             data++;  
  14.     }  
  15. }  
  16.   
  17. class ThreadTest  
  18. {          
  19. blic void RunThreadWithDataInClass()  
  20.     {  
  21.         ThreadData d1 = new ThreadData();  
  22.         Thread t1 = new Thread(d1.RunThread);  
  23.         t1.Start();  
  24.         int i = 0;  
  25.         while (i++ < 1000000)  
  26.             d1.data++;  
  27.         Thread.Sleep(2000);// wait for the new thread to finish  
  28.         Console.WriteLine(string.Format("The data in ThreadData: {0}.", d1.data));  
  29.     }  
  30. }  
這樣的確能夠節省,但是這樣就出現了數據競爭的情況,同理也可以使用AD來實現(AD: Asynchronous Delegate)。

後臺線程與前臺線程的區別:
The process of the application keeps running as long as at least one foreground thread is running. If more
than one foreground thread is running and the Main() method ends, the process of the application remains
active until all foreground threads finish their work.
A thread you create with the Thread class, by default, is a foreground thread. Thread pool threads are
always background threads.
從上面對話來看,AD調用的Thread Pool的線程來執行委託,如果異步委託的宿主也就是caller執行完了, 同時“進程”中沒有其他前臺線程,則其BeginInvoke的委託將會強制關閉。如果是Thread創建的線程,同時沒有修改它成後臺線程,則即使caller結束了,ThreadStart或者ParameterizedThreadStart委託將會繼續進行。所以這裏就出現了必須使用Thread的情況了:當需要創建前臺線程時。

IsBackground
ThreadProperty

這兩者都是Thread纔有的, 而aynchronous delegate沒有的

獲取返回值
從它的構造函數你覺得它憑什麼提供返回值呢,void()() 與 void()(Object obj)來看是沒戲的了。不過可以使用成員函數來實現異步調用,也就是說將需要的參數與異步函數內嵌成一個類,像ThreadData類一樣,成員變量data來儲存異步線程的執行結果,而成員函數RunThread就是需要調用的線程,如果委託(對於ThreadData來說是RunThread函數)費時,則需要在主線程調用Thread的Join函數來實現等待異步線程執行完畢。

至此Thread類就先介紹完畢了。因爲後面有一堆後浪出來了,雖然不能說Thread已經死在灘上了,不過也“個頭近”了。

蹬蹬蹬蹬, ThreadPool粉墨登場了

其實ThreadPool就是系統已經給你準備好一堆thread等待你的委託,而不用讓你管理Thread的細節。
先來看看他有多簡單吧:

[csharp] view plaincopy
  1. public void ThreadProc(Object state)  
  2. {  
  3.     Thread.Sleep(1000);  
  4.     Console.WriteLine(string.Format("Running in ThreadProc, id: {0}.", Thread.CurrentThread.ManagedThreadId));  
  5. }  
  6.   
  7. public void RunSimpleThreadPool()  
  8. {  
  9.     ThreadPool.QueueUserWorkItem(ThreadProc);  
  10.     Console.WriteLine(string.Format("Running in main thread, id: {0}", Thread.CurrentThread.ManagedThreadId));  
  11.     Thread.Sleep(2000);  
  12. }  

在RunSimpleThreadPool添加2秒睡眠是爲了防止主線程結束,後臺(ThreadPool裏的線程都是後臺的)給殺了,所以使用sleep等待一下。
從代碼來看,的確很簡單。不過他委託的函數的格式更加限制了,必須是符合WaitCallback形式,爲void()(Object);
比Thread好不了哪去,之前說類型沒有檢查的問題,其實使用object作爲參數,估計framework的人想給個超超基類指針,的確,object是所有類的老母,所以進去ThreadProc後可以通過GetType來獲取類型。

雖然ThreadPool很好用,但是他的限制條件也挺多的,正如上面所列的:
1. 所有線程都是background,也就意味着前臺結束了,後臺也得跟着倒臺。同時它不提供機會給你修改這個屬性,不能變前臺。
2. 不能設置Priority,想優先不靠譜,還有Name都不能改,挺苦的。
3. For COM objects, all pooled threads are multithreaded apartment (MTA) threads. Many COM objects require a single-threaded apartment (STA) thread。 這個留在後面的篇中講解,尤其是win form的開發過程中。
4. 放進queue裏的委託是不能cancel的。
5. 最後也是個建議,使用ThreadPool執行的委託大多數是短的task, 如果想要生命週期長的,能夠提供很多諸如中斷,suspend等操作,建議使用Thread。

書上對於ThreadPool的使用也是點到這裏,但是作爲一名技術狂熱者,這些是不夠的,來看看使用ThreadPool還能給我們帶來什麼驚喜吧。
先看RegisterWaitForSingleObject的函數定義:

[csharp] view plaincopy
  1. public static RegisteredWaitHandle RegisterWaitForSingleObject(  
  2.       WaitHandle waitObject,  
  3.       WaitOrTimerCallback callBack,  
  4.       Object state,  
  5.       int millisecondsTimeOutInterval,  
  6.       bool executeOnlyOnce  
  7.   )  

一個參數就是需要forcus的handle, 第二就是委託函數,類型爲WaitOrTimerCallback, 而他需要的函數的格式爲 void ()(Object obj, bool timeout),第三個參數就是時間間隔了,最後一個顧名思義,就是是否執行多次,看到這個函數就知道他的作用可以用於執行間隔的查詢功能,因爲第三個參數,當然啦也可以執行永久的,但是正如前面所說的,生命週期長的線程(不管是前臺還是後臺)還是選擇使用Thread,比較靠譜。

先來看使用這個函數實現間隔執行異步的例子:

[csharp] view plaincopy
  1.   class ThreadPoolData  
  2.   {  
  3.       public string Name = "Default";  
  4.       public RegisteredWaitHandle Handle = null;  
  5.       public ThreadPoolData()  
  6.       {   
  7.         
  8.       }  
  9.   
  10.   
  11.   class ThreadPoolTest  
  12.   {  
  13. public void RunRegisterHandle()  
  14.       {   
  15.           AutoResetEvent ev = new AutoResetEvent(false);  
  16.           ThreadPoolData d1 = new ThreadPoolData(){Name = "First"};  
  17.           d1.Handle = ThreadPool.RegisterWaitForSingleObject(ev, ThreadProc, d1, 1000, false);  
  18.           Thread.Sleep(3100);  
  19.           Console.WriteLine("Main thread signals.");  
  20.           ev.Set();  
  21.           Console.WriteLine("Main thread finish.");  
  22.           Thread.Sleep(1000);// wait for the completing the threadproc function  
  23.     Console.ReadLine();  
  24.       }  
  25.   
  26. public void ThreadProc(Object state, bool timeout)  
  27.       {  
  28.           ThreadPoolData d1 = (ThreadPoolData)state;  
  29.           string cause = "TIMED OUT";  
  30.           if (!timeout)  
  31.           {  
  32.               cause = "SIGNALED";  
  33.               if (d1.Handle != null)  
  34.                   d1.Handle.Unregister(null);  
  35.           }  
  36.   
  37.           Console.WriteLine("[0]WaitProc( {0} ) executes on thread {1}; cause = {2}.",  
  38.               d1.Name,  
  39.               Thread.CurrentThread.GetHashCode().ToString(),  
  40.               cause  
  41.           );  
  42.       }  
  43.   }  

例子開始定義一個對象(將需要參數塞進類中,然後再傳遞給異步函數,從而多參數的影響),成員變量有RegisteredWaitHandle,這個是執行RegisterWaitForSingleObject的返回值,這是能否是異步函數能否unregister這個handle的作用,從而從thread queue中撤去,(這個類似Asynchronous Delegate中BeginInvoke的第四個參數經常用其委託函數做參,從而能夠實現在callback中調用返回結果; 或者更像Asynchronous Delegate中使用Handle獲取返回值一幕,大家可以返回看看,主要使用入參嵌入handle成員,同時在異步函數中調用set,實現主線程結束等待)。

接着是啓動函數中調用RegisterWaitForSingleObject的第三個參數是1000,意味着每隔1秒給我跑一次異步函數,注意如果異步函數執行時間超過一秒的話,ThreadPool會選擇另一個新的線程執行委託,所以你會看到同一時刻可能有多個相同的委託正在執行,最後一個參數是false表示執行多次,直到對返回值RegisteredWaitHandle執行Unregister。

最後輸出結果爲:
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
Main thread signals.
[0]WaitProc( First ) executes on thread 4; cause = SIGNALED.
Main thread finish.

如果將Unregister註釋掉了,則輸出結果則成爲:
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
Main thread signals.
[0]WaitProc( First ) executes on thread 4; cause = SIGNALED.
Main thread finish.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
...repeat n times...
出現的原因是由於:
1. 主線程通過執行Console.ReadLine()來阻塞主線程來等待輸入,從而主線程(前臺線程)沒有掉;
2. 沒有執行Unregister函數,ThreadPool就會一直moniter queue中的委託,每次都去執行。

如果將第四個參數改成true,則只執行一次,如果這一秒間隔中沒有發生signaled新號,輸出結果爲:
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
Main thread signals.
Main thread finish.

如果在ThreadProc入口處加入Thread.Sleep(1100), 則輸出結果爲:
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
Main thread signals.
[0]WaitProc( First ) executes on thread 5; cause = TIMED OUT.
[0]WaitProc( First ) executes on thread 4; cause = TIMED OUT.
Main thread finish.
[0]WaitProc( First ) executes on thread 6; cause = SIGNALED.
從上述結果能看出ThreadPool的確每隔“一秒”執行一次委託,不管委託是否還在跑着。

至此,ThreadPool給我們帶來了兩個方法: QueueUserWorkItem以及RegisterWaitForSingleObject;前者是快速執行一些類似task的短任務,是可以使用AsynchronousDelegate的BeginInvoke來實現,後者是提供間隔執行委託任務的方法,到目前爲止,好像也沒有這個介紹,使用情景應該是需要定時查詢操作的業務。


接着闡述一下task類,task的技術支持是靠ThreadPool。

先看看task的優勢介紹:
you can define continuation work — what should be done after a task is complete. This can be differentiated whether the task was successful or not. Also, you can organize tasks in a hierarchy. For example, a parent task can create new children tasks. This can create a dependency, so that canceling a parent task also cancels its child tasks.

先來看看task能支持的異步函數的簽名有Action, Action<Object>,而Action的簽名爲void()(void)或者void()(Object obj), 跟Thread的委託簽名一致。
先看簡單的例子:

[csharp] view plaincopy
  1. public void ThreadProc(string state)  
  2.        {  
  3.            Console.WriteLine("Data : {0}.", state);  
  4.        }  
  5.        public void RunTaskWithParameter()  
  6.        {   
  7.            string str = "luodingjia";  
  8.            Task t = new Task((obj) => {  
  9.                ThreadProc((string)obj);  
  10.            }, (Object)str);  
  11.            t.Start();  
  12.            Console.ReadLine();  
  13.        }  
從這裏能夠看出lambda的作用的方便。

TaskFactory跟Task 對 THreadPool跟Thread有點異曲同工之妙。
例如使用TaskFactory他也不需要手動的調用類似start的函數,而是直接使用Task.Factory.StartNew(Action)的方法,這跟ThreadPool中的QueueItemWorker很相似,立馬執行的。
而Task定義出一個對象之後,還是需要調用Start函數來calling異步委託,這個Thread需要調用Start是一致的。

Task跟TaskFactory都提供類似TaskCreationOptions的枚舉量,具體使用的有PreferFairness, LongRunning(內部使用Thread執行),AttachedToParent。

正如之前所說的Task以及TaskFactory有個很強大的功能就是ContinueTask功能,正如下面例子看到的:

[csharp] view plaincopy
  1. public void TaskProc()  
  2. {  
  3.     Console.WriteLine("Runing TaskProc in ID: {0}.", Task.CurrentId);  
  4.     Thread.Sleep(1000);  
  5. }  
  6. public void TaskProcContinued1(Task t)  
  7. {  
  8.     Console.WriteLine("Runing TaskProcContinued1 in ID: {0} after TaskProc {1}.", Task.CurrentId, t.Status.ToString());  
  9.     Thread.Sleep(1000);  
  10. }  
  11. public void TaskProcContinued2(Task t)  
  12. {  
  13.     Console.WriteLine("Runing TaskProcContinued2 in ID: {0} after TaskProc {1}.", Task.CurrentId, t.Status.ToString());  
  14.     Thread.Sleep(1000);  
  15. }  
  16. public void TaskProcContinued3(Task t)  
  17. {  
  18.     Console.WriteLine("Runing TaskProcContinued3 in ID: {0} after TaskProcContinued1 {1}.", Task.CurrentId, t.Status.ToString());  
  19.     Thread.Sleep(1000);  
  20. }  
  21. public void RunContinueTask()  
  22. {  
  23.     Task t = new Task(TaskProc, TaskCreationOptions.PreferFairness);  
  24.     Task t1 = t.ContinueWith(TaskProcContinued1);  
  25.     Task t2 = t.ContinueWith(TaskProcContinued2);  
  26.     Task t3 = t1.ContinueWith(TaskProcContinued3);  
  27.     t.Start();  
  28.     Console.ReadLine();  
  29. }  

輸出結果:
Runing TaskProc in ID: 1.
Runing TaskProcContinued1 in ID: 3 after TaskProc RanToCompletion.
Runing TaskProcContinued2 in ID: 2 after TaskProc RanToCompletion.
Runing TaskProcContinued3 in ID: 4 after TaskProcContinued1 RanToCompletion.

接着講述Parallel類

針對這個類,主要提供兩個方法族,一個是For,另一個是ForEach。從名字就知道他想幹嘛的了,就是應用於需要同時開始相同的task的時候就可以通過Parallel來實現。
先一個Parallel的簡單的例子:

[csharp] view plaincopy
  1. class ParallelData  
  2. {  
  3.     public int Data = 0;  
  4.     public string Name = "Default";  
  5. }  
  6. class ParallelTest  
  7. {  
  8.     public ParallelTest()  
  9.     {   
  10.       
  11.     }  
  12.   
  13.     public void RunSimpleParallelTest()  
  14.     {  
  15.         ParallelLoopResult result = Parallel.For(0, 10, (int val) =>  
  16.         {  
  17.             Thread.Sleep(3000);  
  18.             Console.WriteLine(string.Format("Index: {0}, TaskId: {1}, ThreadId: {2}.", val, Task.CurrentId, Thread.CurrentThread.ManagedThreadId));  
  19.         });  
  20.         Console.WriteLine(result.IsCompleted);  
  21.     }  
  22. }  

輸出結果爲:
TaskId: , ThreadId: 1.
Index: 0, TaskId: 1, ThreadId: 1.
Index: 2, TaskId: 2, ThreadId: 3.
Index: 4, TaskId: 3, ThreadId: 4.
Index: 6, TaskId: 4, ThreadId: 5.
Index: 8, TaskId: 5, ThreadId: 6.
Index: 1, TaskId: 6, ThreadId: 7.
Index: 3, TaskId: 7, ThreadId: 8.
Index: 5, TaskId: 1, ThreadId: 1.
Index: 7, TaskId: 8, ThreadId: 3.
Index: 9, TaskId: 9, ThreadId: 4.
True

從上述結果能看出幾點:
1. 執行的Index(order)是亂序的,但是能保證[fromInclusive, toExclusive)中的每一個index都平等的執行。
2. 主線程是阻塞於Loop中的,就跟普通的For一樣。
3. Parallel的第三個參數(Action<Int32>)中的輸入參數就是對應的Index,不太像普通的For循環需要指明迭代變量。

使用ParallelLoopState來監控並且控制整個循環,由於每個Loop都會有自動提供,所以不需要自己創建該類的實例,正如下面例子所示,直接在委託中加入ParallelLoopState pls參數,同時在本體中直接使用:

[csharp] view plaincopy
  1. public void RunSimpleParallelTest()  
  2. {  
  3.     DateTime tBgn = DateTime.Now;  
  4.     ParallelData d1 = new ParallelData() { Data = 5 };  
  5.     ParallelLoopResult result = Parallel.For(0, 20, (int val, ParallelLoopState pls) =>  
  6.     {  
  7.         Thread.Sleep(2000);  
  8.         d1.Data++;  
  9.         if (val > 6)  
  10.         {  
  11.             pls.Break();  
  12.             Console.WriteLine(string.Format("Break occurred at {0}, {1}", val, pls.LowestBreakIteration));  
  13.         }  
  14.         Console.WriteLine(string.Format("Index: {0}, Data: {1}, TaskId: {2}, ThreadId: {3}.", val, d1.Data, Task.CurrentId, Thread.CurrentThread.ManagedThreadId));  
  15.     });  
  16.     Console.WriteLine(result.IsCompleted);  
  17.     Console.WriteLine(string.Format("LowBreakIteration: {0}.", result.LowestBreakIteration));  
  18.     DateTime tEnd = DateTime.Now;  
  19.     TimeSpan ts = tEnd - tBgn;  
  20.     Console.WriteLine("Using {0} seconds.", ts.TotalMilliseconds);  
  21.     Console.ReadLine();  
  22. }  
目的是當所在iteration的index大於6則執行break,中斷整個循環, 輸出結果爲:
Index: 0, Data: 6, TaskId: 1, ThreadId: 9.
Index: 5, Data: 7, TaskId: 2, ThreadId: 10.
Break occurred at 10, 10
Break occurred at 15, 15
Index: 10, Data: 9, TaskId: 3, ThreadId: 11.
Index: 15, Data: 9, TaskId: 4, ThreadId: 12.
Index: 1, Data: 10, TaskId: 5, ThreadId: 13.
Index: 6, Data: 11, TaskId: 6, ThreadId: 14.
Index: 2, Data: 12, TaskId: 1, ThreadId: 9.
Break occurred at 7, 7
Index: 7, Data: 13, TaskId: 7, ThreadId: 10.
Index: 4, Data: 14, TaskId: 8, ThreadId: 11.
Break occurred at 9, 7
Index: 9, Data: 15, TaskId: 9, ThreadId: 14.
Index: 3, Data: 16, TaskId: 1, ThreadId: 9.
False
LowBreakIteration: 7.
Using 6015.625 seconds.
從結果上看,得出以下結論:
Break 可用來與需要執行的目前反覆項目之後沒有其他反覆項目的迴圈進行通訊。 例如,如果從 for 迴圈 (以平行方式從 0 到 1000 逐一查看) 的第 100 個反覆項目呼叫 Break,則所有小於 100 的所有反覆項目仍應執行,但從 101 到 1000 的反覆項目則不一定要執行。
對於已長時間執行的反覆運算,如果目前的索引小於 LowestBreakIteration 的目前值,則 Break 會使 LowestBreakIteration 設定為目前反覆項目的索引。

如果將Break函數改成Stop,輸出結果如下:
Break occurred at 10,
Index: 0, Data: 8, TaskId: 1, ThreadId: 1.
Index: 5, Data: 8, TaskId: 2, ThreadId: 3.
Index: 10, Data: 8, TaskId: 3, ThreadId: 4.
False
LowBreakIteration: .
Using 1015.625 seconds.

Stop 可用來與沒有其他反覆項目需要執行的迴圈進行通訊。對於已長時間執行的反覆運算,Stop 會使 IsStopped 針對迴圈的所有其他反覆項目傳回 True,則如果它觀察到 True 值,便會使其他反覆項目檢查 IsStopped 並提早結束。Stop 通常會在以搜尋為基礎的演算法中採用,其中一旦找到位置,則不需要執行其他反覆項目。

上述結論都是從MSDN抄過來,都不太好理解,本人先跳過去了。
接着有類似於Parallel.For的姊妹Parallel.ForEach,後者跟前者很相似,先看例子:

[csharp] view plaincopy
  1. public void RunParallelForEach()  
  2. {  
  3.     List<int> intArr = new List<int>();  
  4.     Random rdn = new Random(DateTime.Now.TimeOfDay.Milliseconds);  
  5.     for (int i = 0; i < 20; i++)  
  6.     {  
  7.         intArr.Add(rdn.Next(100));    
  8.     }  
  9.   
  10.     Parallel.ForEach<int>(intArr, (val, pls, index) => {  
  11.         Console.WriteLine(string.Format("intArr[{0}]: {1}.", index, val));  
  12.     });  
  13.   
  14.     Console.WriteLine("Finish");  
  15.     Console.ReadLine();  
  16. }  

輸出結果爲:
intArr[0]: 56.
intArr[5]: 87.
intArr[15]: 9.
intArr[16]: 48.
intArr[17]: 27.
intArr[18]: 34.
intArr[19]: 87.
intArr[4]: 9.
intArr[3]: 65.
intArr[11]: 6.
intArr[6]: 44.
intArr[7]: 10.
intArr[13]: 45.
intArr[14]: 77.
intArr[12]: 48.
intArr[10]: 56.
intArr[8]: 72.
intArr[9]: 56.
intArr[1]: 42.
intArr[2]: 16.
Finish

跟For一樣,是亂序,Action爲第一個參數是容器元素,第二個是parallel的state控制參數,第三個是iteration,也即是容器的索引。

並行編程的同步

1. 使用lock關鍵字(注意只針對reference type也就是類之類的對象,如果是int就不行了,那是值對象),代碼如下:

[csharp] view plaincopy
  1. class SynchronousData  
  2. {  
  3.     public int Data = 0;  
  4.     public string Description = "Default";  
  5. }  
  6. class SynchronousTest  
  7. {  
  8.     public SynchronousTest()  
  9.     {   
  10.       
  11.     }  
  12.   
  13.     private void TaskProc(Object state)  
  14.     {  
  15.         SynchronousData d = state as SynchronousData;  
  16.         if (d != null)  
  17.         {  
  18.             int i = 0;  
  19.             while (i++ < 1000000)  
  20.             {   
  21.                 <span style="color:#FF0000;">lock(d)</span>  
  22.                 {  
  23.                     d.Data++;  
  24.                 }  
  25.             }  
  26.             Console.WriteLine(string.Format("Finish in task id {0}, thread id {1}.", Task.CurrentId, Thread.CurrentThread.ManagedThreadId));  
  27.         }  
  28.     }  
  29.     public void RunSimpleSynchrounousTestInTask()  
  30.     {  
  31.         SynchronousData d1 = new SynchronousData() { Data = 5 };  
  32.         Task t1 = new Task(TaskProc, d1);  
  33.         Task t2 = new Task(TaskProc, d1);  
  34.         t1.Start();  
  35.         t2.Start();  
  36.         t1.Wait();  
  37.         t2.Wait();  
  38.         Console.WriteLine(d1.Data);  
  39.         Console.ReadLine();  
  40.     }  
  41. }  

2. interlocked

使用interlocked是比其他同步方法都要快的,但是能夠實現的用途受其成員函數的限制, 對於上述的例子可以使用interlocked.Increment來代替,將

[csharp] view plaincopy
  1. lock(d)  
  2. {  
  3.     d.Data++;  
  4. }  
替換成:

[csharp] view plaincopy
  1. Interlocked.Increment(ref d.Data);  
3. Moniter

Moniter使用Enter以及Exit函數(類似Mutex一樣)來獲取有限資源,他跟lock類似,但是他比lock好處是他可以使用TryEnter來指定等待時間,如果等待時間超出了,可以執行別的任務,下面是展示代碼:

[csharp] view plaincopy
  1. bool lockTaken = false;  
  2. Monitor.TryEnter(obj, 500, ref lockTaken);  
  3. if (lockTaken)  
  4. {  
  5.    try  
  6.    {  
  7.       // acquired the lock  
  8.       // synchronized region for obj  
  9.    }  
  10.    finally  
  11.    {  
  12.       Monitor.Exit(obj);  
  13.    }  
  14. }  
  15. else  
  16. {  
  17.    // didn't get the lock, do something else  
  18. }     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章