程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> C#之任務,線程和同步

C#之任務,線程和同步

編輯:C#基礎知識

1 概述

  對於所有需要等待 的操作,例 如 ,因 為文件 、 數據庫或網絡訪 問都需要一定 的時間,此 時就可以啟 動一個新線程,同時完成其他任務,即使是處理密集型的任務,線程也是有幫助的。

2 Parallel類

  2.1 用Parallel.For()方法循環

  Parallel.For()方法類似於C#的For循環,多次執行一個任務,它可以並行運行迭代。迭代的順序沒有定義。

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

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

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

  2.2 用Parallel.ForEach()方法循環

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

     string[] data = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve" };
             //  Parallel.ForEach(data, s => { Console.WriteLine(s); });
             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類 的構造函數在創建任務上提供的靈活性較大.

    //using TaskFactory
             Task t1 = new TaskFactory().StartNew(TaskMethod);
             //using the task factory via task
             Task t2 = Task.Factory.StartNew(TaskMethod);
             //using task constructor
             Task t3 = new Task(TaskMethod);
             t3.Start();

 

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

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

  3.2連續的任務

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

  static void DoOnFirst()
         {
             Console.WriteLine("doing some task {0}",Task.CurrentId);
             Thread.Sleep(3000);
         }
         static void DoSecond(Task t)
         {
             Console.WriteLine("task {0} finished",t.Id);
             Console.WriteLine("this task id {0}",Task.CurrentId);
             Console.WriteLine("do some cleanup");
             Thread.Sleep(3000);
         }
 
  Task t1 = new Task(DoOnFirst);
             Task t2 = t1.ContinueWith(DoSecond);
             Task t3 = t2.ContinueWith(DoSecond);
             Task t4 = t3.ContinueWith(DoSecond);
             Task t5 = t4.ContinueWith(DoSecond,TaskContinuationOptions.PreferFairness);
             t1.Start();

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

  3.3任務層次的結構

  static void ParentAndChild()
         {
             var parent = new Task(ParentTask);
             parent.Start();
             Thread.Sleep(2000);
             Console.WriteLine(parent.Status);
             Thread.Sleep(4000);
             Console.WriteLine(parent.Status);
             Console.WriteLine();
         }
         private static void ParentTask()
         {
             Console.WriteLine("task id {0}",Task.CurrentId);
             var child = new Task(ChildTask);
             child.Start();
             Thread.Sleep(1000);
             Console.WriteLine("parent started child");
         }
 
         private static void ChildTask()
         {
             Console.WriteLine("child");
             Thread.Sleep(5000);
             Console.WriteLine("child finished");
         }

如果父任務在子任務之前結束 ,父 任務的狀態就顯示為WaitingForChildrenToComplete.只要子任務也結束 時,父任務的狀態就變成RanToCompletion。 ·

4 取消架構

  4.1Parallel.For()方法的取消

 var cts = new CancellationTokenSource();
             cts.Token.Register(() => Console.WriteLine("token canceled"));
             new Task(() => { Thread.Sleep(500); cts.Cancel(false); }).Start();
             try
             {
                 ParallelLoopResult result = Parallel.For(0, 100, new ParallelOptions() { CancellationToken = cts.Token, }, x =>
                 {
                     Console.WriteLine("loop {0} started", x);
                     int sun = 0;
                     for (int i = 0; i < 100; i++)
                     {
                         Thread.Sleep(2);
                         sun += i;
                     }
                     Console.WriteLine("loop {0} finished",x);
                 });
             }
             catch (Exception ex)
             {
                 Console.WriteLine(ex.Message);
             }

  4.2任務的取消

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

5 線程池

  如果有不同的小任務要完成,就可以事先創建許多線程 ,· 在應完成這些任務時發出請求。 這個線程數最好在需要更多的線程時增加,在 需要釋放資源時減少。不需要自己創建這樣一個列表。 該列表由 ThreadPool類 托管。 這個類會在需要時增減池中線程的線程數,直 到最大的線程數。 池中的最大線程數是可配置的。如果有更多的作業要處理,線 程池中線程的個數也到了極限,最 新的作業就要排隊,且 必須等待線程完成其任務。

  static void Main(string[] args)
         {
             int nWorkerThreads;
             int nCompletionPortThreads;
             ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);
             Console.WriteLine("nWorkerThreads:{0},nCompletionPortThreads:{1}", nWorkerThreads, nCompletionPortThreads);
 
             for (int i = 0; i < 5; i++)
             {
                 ThreadPool.QueueUserWorkItem(JobForAThread);
             }
             Thread.Sleep(3000);
             Console.ReadKey();
         }
         static void JobForAThread(object obj)
         {
             for (int i = 0; i < 3; i++)
             {
                 Console.WriteLine("loop:{0},running inside pooled thread{1}",i,Thread.CurrentThread.ManagedThreadId);
                 Thread.Sleep(30);
             }
         }
View Code

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

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

6 Therd類

使用Thread類可以創建和控制線程,

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

  6.1給後台線程傳遞數據

  給線程傳遞一些數據可以采用2中方式,一種是使用帶ParameterizdThreadStart委托參數的Thread構造函數,另一種方式是常見一個自定義的類,把線程的方法定義為實例方法。

  6.2後台任務

  只要有一個前台相稱在運行,程序的進程就在運行,如果前台多個線程在運行,而Main()方法結束了,應用程序的進程直到所有前台完成其任務前都處於激活狀態。默認情況下,用Thread創建的線程為前台線程,線程池中的線程為總是為後台線程。Thread類可以設置IsBackground屬性設置是否為前台線程。

 static void Main(string[] args)
         {
             var t1 = new Thread(ThreadMain) { Name = "NewThread", IsBackground = false };
             t1.Start();
             Console.WriteLine("Main thread ending now");
             Console.ReadKey();
         }
 
 
         static void ThreadMain()
         {
             Console.WriteLine("Thread {0} statrted",Thread.CurrentThread.Name);
             Thread.Sleep(5000);
             Console.WriteLine("Thread {0} completed",Thread.CurrentThread.Name);
         }
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語 句是設置鎖定和解除鎖定的一種簡單方式。

  static void Main()
         {
             int numTask = 20;
             var state = new ShareState();
             var tasks = new Task[numTask];
             for (int i = 0; i < numTask; i++)
             {
                 tasks[i] = new Task(new Job(state).DoWork);
                 tasks[i].Start();
             }
 
             for (int i = 0; i < numTask; i++)
             {
                 tasks[i].Wait();
             }
             Console.WriteLine("Sun :{0}",state.State);
             Console.ReadKey();
         }
     }
     public class Job
     {
         ShareState shareState;
         public Job(ShareState shareState)
         {
             this.shareState = shareState;
         }
         public void DoWork()
         {
             for (int i = 0; i < 5000; i++)
             {
                 shareState.State += 1;
             }
         }
     }
     public class ShareState
     {
         public int State { get; set; }
     }
View Code

上面的代碼,因為執行了5000次循環,有20個任務,所以輸出的值應為100000,但是,事實並非如此。使用Lock修改DoWork方法

   public void DoWork()
         {
             for (int i = 0; i < 5000; i++)
             {
                 lock (shareState)
                 shareState.State += 1;
             }
         }
View Code

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

   static void Main()
         {
             int numTask = 20;
             var state = new ShareState();
             var tasks = new Task[numTask];
             for (int i = 0; i < numTask; i++)
             {
                 tasks[i] = new Task(new Job(state).DoWork);
                 tasks[i].Start();
             }
 
             for (int i = 0; i < numTask; i++)
             {
                 tasks[i].Wait();
             }
             Console.WriteLine("Sun :{0}", state.State);
             Console.ReadKey();
         }
     }
     public class Job
     {
         ShareState shareState;
         public Job(ShareState shareState)
         {
             this.shareState = shareState;
         }
         public void DoWork()
         {
             for (int i = 0; i < 5000; i++)
             {
                 shareState.IncrementState();
             }
         }
     }
     public class ShareState
     {
         private int state = 0;
         private object obj = new object();
         public int State
         {
             get
             {
                 return state;
             }
         }
         public int IncrementState()
         {
             lock(obj)
                 return ++state;
         }
     }
View Code

  8.2 Interlocked類

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

  8.3 Monitor類

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

 object obj = new object();
             bool lockTaken = false;
             Monitor.TryEnter(obj, 500, ref lockTaken);
             if (lockTaken)
             {
                 try
                 {
                     //已經鎖定,想干嘛就干嘛吧
                 }
                 finally
                 {
                     Monitor.Exit(obj);
                 }
             }
             else
             {
                 //沒有鎖定,小心喽
             }
View Code

  8.4 Mutex類

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

   static class Program
     {
         /// <summary>
         /// 應用程序的主入口點。
         /// </summary>
         [STAThread]
         static void Main()
         {
             bool createNew;
 
             Mutex m = new Mutex(false, "test", out createNew);
             if (!createNew)
             {
                 MessageBox.Show("程序已啟動");
                 Application.Exit();
                 return;
             }
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             Application.Run(new Form1());
         }
     }
View Code 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved