C#之任務,線程和同步

1 概述

  對於全部須要等待 的操做,例 如 ,因 爲文件 、 數據庫或網絡訪 問都須要必定 的時間,此 時就能夠啓 動一個新線程,同時完成其餘任務,即便是處理密集型的任務,線程也是有幫助的。數據庫

2 Parallel類

  2.1 用Parallel.For()方法循環

  Parallel.For()方法相似於C#的For循環,屢次執行一個任務,它能夠並行運行迭代。迭代的順序沒有定義。安全

1 ParallelLoopResult result = Parallel.For(0, 10, i =>
2             {
3                 Console.WriteLine("{0},task:{1},thread:{2}", i, Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
4                 Thread.Sleep(10);
5             });
6             Console.WriteLine(result.IsCompleted);

  在For()方法中,前兩個參數定義了循環的開頭和結束。從輸出能夠看出,順序是不能保證的。也能夠提早中斷Parallel.For()方法。網絡

1 ParallelLoopResult result2 = Parallel.For(10, 40, (int i,ParallelLoopState pls) =>
2             {
3                 Console.WriteLine("i: {0},task:{1}", i, Task.CurrentId);
4                 Thread.Sleep(10);
5                 if (i > 15)
6                     pls.Break();
7             });
8             Console.WriteLine(result2.IsCompleted);
9             Console.WriteLine( "lowest break iteration:{0}",result2.LowestBreakIteration);

  2.2 用Parallel.ForEach()方法循環

  paraller.ForEach()方法遍歷實現了IEnumerable的集合,其方式相似於Foreach語句,但以異步方式遍歷,這裏也沒有肯定的遍歷順序。多線程

1     string[] data = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve" };
2             //  Parallel.ForEach(data, s => { Console.WriteLine(s); });
3             Parallel.ForEach(data, (s, pls) => { if (s == "one") { Console.WriteLine("break......"); pls.Break(); } Console.WriteLine(s); Thread.Sleep(100); });

  2.3 經過Paraller.Invoke()調用多個方法

 Parallel.Invoke(Foo ,Bar);

 static void Foo() { Console.WriteLine("foo"); }
 static void Bar() { Console.WriteLine("bar"); }

3 任務

  .NET 4 包含新的名稱空間System.Threading.Task,它它 包含的類抽象出了線程功能,在後臺使用ThreadPool。 任務表示應完成的某個單元的工做。 這個單元的工做能夠在單獨的線程中運行,也能夠以同步方式啓動一個任務,這須要等待主調線程。架構

  3.1啓動任務

  要啓動任務,可 以使用 TaskFactory類 或 Task類 的構造函數和 start()方 法。 Task類 的構造函數在建立任務上提供的靈活性較大.異步

1    //using TaskFactory
2             Task t1 = new TaskFactory().StartNew(TaskMethod);
3             //using the task factory via task
4             Task t2 = Task.Factory.StartNew(TaskMethod);
5             //using task constructor
6             Task t3 = new Task(TaskMethod);
7             t3.Start();

 

使用 Task類 的構造函數和 TaskFactory類 的 stamw()方法時,均可以傳遞TaskCreationOptions枚舉中的值。 設置LongRunning選項,可 以通知任務調度器,該 任務須要較長時間執行,這樣調度器更可能使用 新線。 若是該任務應關聯到父任務上,而父任務取消了,則 該任務也應取消,此 時應設置 AuachToParent選 項。PerferFairness 值表示,調度器應提取出已在等待的第一個任務。  若是任務使用 子任務建立了其餘工做,子
任務就優先於其餘任務。 它們不會排在線程池隊列中的最後。 若是這些任務應 以公平的方式與全部其餘任務一塊兒處理,就設置該選項爲PreferFairnesside

1   Task t5 = t4.ContinueWith(DoSecond,TaskContinuationOptions.PreferFairness);

  3.2連續的任務

  經過任務,可 以指定在任務完成後,應 開始運行另外一個特定任務.函數

 1  static void DoOnFirst()
 2         {
 3             Console.WriteLine("doing some task {0}",Task.CurrentId);
 4             Thread.Sleep(3000);
 5         }
 6         static void DoSecond(Task t)
 7         {
 8             Console.WriteLine("task {0} finished",t.Id);
 9             Console.WriteLine("this task id {0}",Task.CurrentId);
10             Console.WriteLine("do some cleanup");
11             Thread.Sleep(3000);
12         }
13 
14  Task t1 = new Task(DoOnFirst);
15             Task t2 = t1.ContinueWith(DoSecond);
16             Task t3 = t2.ContinueWith(DoSecond);
17             Task t4 = t3.ContinueWith(DoSecond);
18             Task t5 = t4.ContinueWith(DoSecond,TaskContinuationOptions.PreferFairness);
19             t1.Start();

不管前一個任務是如何結束的,前 面 的連續任務老是在前一個任務結束時啓 動 。 使用TaskContinuationOptions 枚舉中的值,可 以指定,連續任務只有在起始任務成功(或失敗)結束時啓動。oop

  3.3任務層次的結構

 1  static void ParentAndChild()
 2         {
 3             var parent = new Task(ParentTask);
 4             parent.Start();
 5             Thread.Sleep(2000);
 6             Console.WriteLine(parent.Status);
 7             Thread.Sleep(4000);
 8             Console.WriteLine(parent.Status);
 9             Console.WriteLine();
10         }
11         private static void ParentTask()
12         {
13             Console.WriteLine("task id {0}",Task.CurrentId);
14             var child = new Task(ChildTask);
15             child.Start();
16             Thread.Sleep(1000);
17             Console.WriteLine("parent started child");
18         }
19 
20         private static void ChildTask()
21         {
22             Console.WriteLine("child");
23             Thread.Sleep(5000);
24             Console.WriteLine("child finished");
25         }

若是父任務在子任務以前結束 ,父 任務的狀態就顯示爲WaitingForChildrenToComplete.只要子任務也結束 時,父任務的狀態就變成RanToCompletion。 ·this

4 取消架構

  4.1Parallel.For()方法的取消

 1 var cts = new CancellationTokenSource();
 2             cts.Token.Register(() => Console.WriteLine("token canceled"));
 3             new Task(() => { Thread.Sleep(500); cts.Cancel(false); }).Start();
 4             try
 5             {
 6                 ParallelLoopResult result = Parallel.For(0, 100, new ParallelOptions() { CancellationToken = cts.Token, }, x =>
 7                 {
 8                     Console.WriteLine("loop {0} started", x);
 9                     int sun = 0;
10                     for (int i = 0; i < 100; i++)
11                     {
12                         Thread.Sleep(2);
13                         sun += i;
14                     }
15                     Console.WriteLine("loop {0} finished",x);
16                 });
17             }
18             catch (Exception ex)
19             {
20                 Console.WriteLine(ex.Message);
21             }

  4.2任務的取消

   一樣的取消模式也可用於任務。

5 線程池

  若是有不一樣的小任務要完成,就能夠事先建立許多線程 ,· 在應完成這些任務時發出請求。 這個線程數最好在須要更多的線程時增長,在 須要釋放資源時減小。不須要本身建立這樣一個列表。 該列表由 ThreadPool類 託管。 這個類會在須要時增減池中線程的線程數,直 到最大的線程數。 池中的最大線程數是可配置的。若是有更多的做業要處理,線 程池中線程的個數也到了極限,最 新的做業就要排隊,且 必須等待線程完成其任務。

 1  static void Main(string[] args)
 2         {
 3             int nWorkerThreads;
 4             int nCompletionPortThreads;
 5             ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);
 6             Console.WriteLine("nWorkerThreads:{0},nCompletionPortThreads:{1}", nWorkerThreads, nCompletionPortThreads);
 7 
 8             for (int i = 0; i < 5; i++)
 9             {
10                 ThreadPool.QueueUserWorkItem(JobForAThread);
11             }
12             Thread.Sleep(3000);
13             Console.ReadKey();
14         }
15         static void JobForAThread(object obj)
16         {
17             for (int i = 0; i < 3; i++)
18             {
19                 Console.WriteLine("loop:{0},running inside pooled thread{1}",i,Thread.CurrentThread.ManagedThreadId);
20                 Thread.Sleep(30);
21             }
22         }
View Code

  線程池使用起來很簡單,但 它有一些限制 :

  • 線程池 中 的全部線程都是後 臺線程 。 如 果進程 的全部前 臺線程都結束 了,所 有 的後 臺線程就會中止 。 不能把入池的線程改成前臺線程 。
  • 不 能給入池的線程設置優先級或名 稱 。
  • 入池的線程只能用 於時間較短的任務 。 如 果線程要一直運行(如 Word的 拼寫檢查器線程),就應使用 Therd類創 建一個線程 

6 Therd類

使用Thread類能夠建立和控制線程,

1 new Thread(() => { Console.WriteLine("Running in thread"); }).Start();
2             Console.WriteLine("this is the main thread");

  6.1給後臺線程傳遞數據

  給線程傳遞一些數據能夠採用2中方式,一種是使用帶ParameterizdThreadStart委託參數的Thread構造函數,另外一種方式是常見一個自定義的類,把線程的方法定義爲實例方法。

  6.2後臺任務

  只要有一個前臺相稱在運行,程序的進程就在運行,若是前臺多個線程在運行,而Main()方法結束了,應用程序的進程直到全部前臺完成其任務前都處於激活狀態。默認狀況下,用Thread建立的線程爲前臺線程,線程池中的線程爲老是爲後臺線程。Thread類能夠設置IsBackground屬性設置是否爲前臺線程。

 1 static void Main(string[] args)
 2         {
 3             var t1 = new Thread(ThreadMain) { Name = "NewThread", IsBackground = false };
 4             t1.Start();
 5             Console.WriteLine("Main thread ending now");
 6             Console.ReadKey();
 7         }
 8 
 9 
10         static void ThreadMain()
11         {
12             Console.WriteLine("Thread {0} statrted",Thread.CurrentThread.Name);
13             Thread.Sleep(5000);
14             Console.WriteLine("Thread {0} completed",Thread.CurrentThread.Name);
15         }
View Code

  6.3線程的優先級

  線 程曲操做系統調度。 給線程指定優先級,就 可 以影響調度順序。在Thread類中,能夠設置Priority屬性設置線程的優先級,Priority屬性須要ThreadPriority枚舉定義的一個值,定義級別有Highest,AboveNormal,Normal,BelowNormal和Lowest。

  6.4控制線程

  調用 Thread對 象的Start()方 法,可 以建立線程。 可是,在 調用Strat()方法後,新線程仍不是處於 Running狀態,而 是處於 Unstarted狀 態。 只要操做系統的線程調度器選擇了要運行的線程,線程就會改成Running狀態 。 讀取Thread.ThreadState屬 性,就能夠得到線程的當前狀態。使用 Thread.Sleep() 方法 ,會使線程處於WaitSleepJoin狀態,在 經歷Sleep()方法定義的時間段後 ,線程就會等待再次被喚醒。要中止另外一個線程,可 以調用Thread.Abort()方 法。 調用這個方法時,會 在接到終止命令的線程中拋出一個ThreadAbortException類 型的異常。 用一個處理程序捕獲這個異常,線程可 以在結束前完成一些清理工做。如 果須要等待線程的結束,就 可 以調用Thread.Join()方 法 。此方 法會中止當前線程 ,並把它設置爲WaitSleepJoin狀 態 ,直 到加入 的線程完成爲止 。

7線程問題

  7.1爭用條件

  若是兩個或多個線程訪問相同的對象,或 者訪問不一樣步的共享狀態,就會出現爭用條件。

  7.2死鎖

  過多的鎖定也會有麻煩。 在死鎖中,至少有兩個線程被掛起,並等待對方解除鎖定。 因爲兩個線程都在等待對方,就 出現了死鎖,線程將無限等待下去。

8 同步

  8.1 Lock語句和線程安全

  C#爲多個線程的同步提供了 本身的關鍵字:lock語 句 。 lock語 句是設置鎖定和解除鎖定的一種簡單方式。

 1  static void Main()
 2         {
 3             int numTask = 20;
 4             var state = new ShareState();
 5             var tasks = new Task[numTask];
 6             for (int i = 0; i < numTask; i++)
 7             {
 8                 tasks[i] = new Task(new Job(state).DoWork);
 9                 tasks[i].Start();
10             }
11 
12             for (int i = 0; i < numTask; i++)
13             {
14                 tasks[i].Wait();
15             }
16             Console.WriteLine("Sun :{0}",state.State);
17             Console.ReadKey();
18         }
19     }
20     public class Job
21     {
22         ShareState shareState;
23         public Job(ShareState shareState)
24         {
25             this.shareState = shareState;
26         }
27         public void DoWork()
28         {
29             for (int i = 0; i < 5000; i++)
30             {
31                 shareState.State += 1;
32             }
33         }
34     }
35     public class ShareState
36     {
37         public int State { get; set; }
38     }
View Code

上面的代碼,由於執行了5000次循環,有20個任務,因此輸出的值應爲100000,可是,事實並不是如此。使用Lock修改DoWork方法

1   public void DoWork()
2         {
3             for (int i = 0; i < 5000; i++)
4             {
5                 lock (shareState)
6                 shareState.State += 1;
7             }
8         }
View Code

這樣結果老是正確的。可是在一個地方使用Lock語句並不意味着,訪問對象的其餘線程都在等待,必須對每一個訪問共享狀態的線程顯示的使用同步功能。繼續需改

 1   static void Main()
 2         {
 3             int numTask = 20;
 4             var state = new ShareState();
 5             var tasks = new Task[numTask];
 6             for (int i = 0; i < numTask; i++)
 7             {
 8                 tasks[i] = new Task(new Job(state).DoWork);
 9                 tasks[i].Start();
10             }
11 
12             for (int i = 0; i < numTask; i++)
13             {
14                 tasks[i].Wait();
15             }
16             Console.WriteLine("Sun :{0}", state.State);
17             Console.ReadKey();
18         }
19     }
20     public class Job
21     {
22         ShareState shareState;
23         public Job(ShareState shareState)
24         {
25             this.shareState = shareState;
26         }
27         public void DoWork()
28         {
29             for (int i = 0; i < 5000; i++)
30             {
31                 shareState.IncrementState();
32             }
33         }
34     }
35     public class ShareState
36     {
37         private int state = 0;
38         private object obj = new object();
39         public int State
40         {
41             get
42             {
43                 return state;
44             }
45         }
46         public int IncrementState()
47         {
48             lock(obj)
49                 return ++state;
50         }
51     }
View Code

  8.2 Interlocked類

  Ihterlockcd類用 於使變量的簡單語旬原子化。 i++不是線程安全的,它 的操做包括從內存中獲取一個值,給該值遞增 1,再 將它存儲回內存。 這些操做均可能會被線程調度器打斷。 Ihterlocked類提供了以線程安全的方式遞增、 遞減、'交換和讀取值的方法。 與其餘同步技術相 比,使用 Ihterlocked類 會快得多。 可是,它 只能用於簡單的同步問題。

  8.3 Monitor類

  C#的lock語 句 ,由編譯器解析爲使用monitor類,與C#的 lock語 句相 比,Monitor 類的主要優勢是:可 以添加一個等待被鎖定的超時值 。 這樣就不會無限期地等待被鎖定.

 1 object obj = new object();
 2             bool lockTaken = false;
 3             Monitor.TryEnter(obj, 500, ref lockTaken);
 4             if (lockTaken)
 5             {
 6                 try
 7                 {
 8                     //已經鎖定,想幹嗎就幹嗎吧
 9                 }
10                 finally
11                 {
12                     Monitor.Exit(obj);
13                 }
14             }
15             else
16             {
17                 //沒有鎖定,當心嘍
18             }
View Code

  8.4 Mutex類

  Mutex【Mutual exclusion ,互 斥)是.Net Freamwork中 提供跨多個進程同步訪問的一個類 因爲系統能識別有名稱的互斥,因 此可 以使用 它禁止應用程序啓動兩次

 1   static class Program
 2     {
 3         /// <summary>
 4         /// 應用程序的主入口點。
 5         /// </summary>
 6         [STAThread]
 7         static void Main()
 8         {
 9             bool createNew;
10 
11             Mutex m = new Mutex(false, "test", out createNew);
12             if (!createNew)
13             {
14                 MessageBox.Show("程序已啓動");
15                 Application.Exit();
16                 return;
17             }
18             Application.EnableVisualStyles();
19             Application.SetCompatibleTextRenderingDefault(false);
20             Application.Run(new Form1());
21         }
22     }
View Code 
相關文章
相關標籤/搜索