C#異步編程

  在使用多線程編寫端口掃描程序時,我本身感受同步和肯定全部線程都執行完的時間是2個比較麻煩的問題。有園友評論說如今已經不手動建立thread對象了,而是直接使用Task異步方式,個人網絡編程老師也講到了異步編程的優越性。在學習了課本上的知識後,進行了一個總結分享給你們。從.NET4.5開始,用async和await關鍵字再加上Task.Run是一個很是不錯的異步編程模型。編程

1.await和async服務器

  異步模式從技術上看就是利用委託來實現的,它的主要好處是在異步執行的過程當中,用戶仍然能夠操控UI界面。使用Task類和使用Thread類有不少類似的地方,Task類也是經過調用方法去實現一個任務的完成,方法但是是命名方法或匿名方法,在執行過程當中可以使用async和await來實現異步執行。async是一個修飾符,它只能用在方法或者事件處理程序的簽名中。對於方法可分爲有返回值和無返回值兩種狀況,事件則只有一種,以下面三條語句所示:網絡

    private async Task<int> MethodAsync();//有返回值的異步方法多線程

    private async Task MethodAsync();//無返回值的異步方法異步

    private async void btnOk_Click();//異步事件處理程序async

  await是一個運算符,它表示等待異步執行的結果。也能夠理解爲await運算符其實是對方法的返回值進行操做,也就是對Task<Result>進行操做,而不是對方法自己進行操做。還有一點要注意,await是必定要放在異步方法的內部,若是沒有放在內部的話,VS會自動報錯。如下是async和await使用的例子: 異步編程

    private async void button5_Click(object sender, EventArgs e)
    {
      Task a = Method1Async();
      //此處可繼續執行其餘代碼
      await a;//等待任務a完成
      Task<int> b = Method2Async();
      //此處可繼續執行其餘代碼
      int c = await b;//等待任務b完成,且能夠拿到任務b的返回值
    }函數

    Task Method1Async();
    async Task<int> Method2Async()
    {
      await Task.Delay(100);
      return 1;
    }學習

  await和同步編程最大的不一樣之處是:異步等待任務完成的時候,在不會繼續執行後面的代碼時,也不會影響界面的操做。在.NET提供的類中,異步方法都是約定用Async做爲後綴,這樣能夠很清楚的知道這個方法是異步方法仍是同步方法。spa

2. 建立任務

  建立任務也就是將任務與要執行的方法聯繫起來,編寫任務執行的方法時,這個方法既能夠是同步方法也能夠是異步方法,還能夠是匿名方法。執行異步方法時,必須用async和Task共同表示沒有返回值的任務,用async和Task<TResult>共同表示返回值爲TResult的任務。如下是定義執行任務的方法。

    private async void button5_Click(object sender, EventArgs e)
    {

      //Task.Run方法表示使用默認的任務調度程序在線程池中經過後臺執行指定的任務

      //若是不須要本身去調度方法,使用這個方式最方便
      await Task.Run(()=>Method1Async());//執行同步方法
      int c = await Task.Run(()=>Method2Async());//執行異步方法
      await Task.Run(async () => { c = 2; });//執行異步匿名方法
    }
    void Method1Async();
    async Task<int> Method2Async(){...}

  Task.Run方法經常使用的重載形式有如下4種,另外它也是能夠用new關鍵字顯示建立任務,可是這種方式用的很少。

    Task Run(Func<Task> function);//執行不帶返回值的任務

    Task<TResult> Run<TResult>(Func<Task<TResult>> function);//執行帶返回值的任務

    Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken);//執行過程當中能夠監聽取消通知

    Task Run(Func<Task> function, CancellationToken cancellationToken);//執行過程當中能夠監聽取消通知

3. 終止任務

  在執行任務時確定會出現須要終止任務的狀況,這裏的終止告訴任務你要儘快停下來再也不執行了,而不是直接銷燬任務實例。這裏能夠打個比方,學生一塊兒出去吃飯了,學生與老師都在班羣裏面,忽然班羣里老師說要讓同窗們集合,若是全部同窗都看到這個消息,而後學生們開始出發,這樣就能夠正確的集合。上面的例子有一個很重要的前提,那就是全部同窗都要看到這個消息,也就是學生是時刻監聽消息的。CancellationTokenSource類和CancellationToken結構用於實現多線程、線程池和Task任務的取消操做,處理模式與上面的例子類似。建立的班羣就是CancellationTokenSource對象,收到的通知就是CancellationToken對象。CancellationTokenSource用於建立取消通知,CancellationToken則用於傳播應取消操做的通知,當調用任務前,能夠先建立取消源對象CancellationTokenSource cts=new CancellationTokenSource();,若是但願在30秒後自動發出取消通知,能夠傳入參數CancellationTokenSource(TimeSpan.FromSeconds(30));CancellationToken ct=cts.Token;,後一句代碼是拿到取消的通知。CancellationTokenSource還有一個Cancel方法,將這個屬性設爲true時,該方法會將全部添加了取消標記的CancellationToken對象的IsCancellationRequested屬性都設置爲true,這樣取消通知就傳遞到了正在執行的任務。

  任務收到取消通知後,能夠選擇兩種方式來終止操做。第一種方式是簡單的從委託返回。這種實現方式相似於在調用任務的代碼中一個bool值來表示取消通知,任務收到後就直接返回了。當採用這種方式時任務狀態的返回值爲TaskStatus.RanToCompletion枚舉值,它表示正常完成,而不是TaskStatus.Canceled枚舉值。第二種方式是在代碼裏引起OperationCanceledException異常,並將其傳遞到在其上請求了取消的標記,採用這種方式取消的任務會轉換爲用Canceled枚舉值表示的狀態。完成引起異常的首選方式是調用ct.ThrowIfCancellationRequestes();。如下是代碼示例,寫了一個winform程序,利用進度條來取消任務。第一個圖是沒有引起異常時,程序退出for循環,執行後面的代碼後返回了,第二張圖是第二種方式,引起了異常後直接跳轉到catch語句塊了。

     CancellationTokenSource cts; private async void button3_Click(object sender, EventArgs e) { progressBar1.Maximum = 100; progressBar1.Value = 0; cts = new CancellationTokenSource(); var aa = MYThreadAsync("a", cts.Token); try { await aa; listBox1.Items.Add("await後面"); } catch { if (aa.IsCanceled) listBox1.Items.Add("a取消"); } } private void button4_Click(object sender, EventArgs e) { cts.Cancel(); } public async Task MYThreadAsync(string s, CancellationToken ct) { for (int i = 0; i < 50; i++) { if (ct.IsCancellationRequested) break;          //點擊關閉按鈕,IsCancellationRequested就爲true,就會退出for循環,這是第一種方式
                progressBar1.Value += 2; await Task.Delay(100); ct.ThrowIfCancellationRequested();//這是第二種方式,它會終止任務而且返回catch語句塊裏面
 } listBox1.Items.Add("任務" + s + "完成了"); }

4. 獲取任務執行的狀態

  在異步編程中,很顯然任務執行的狀態是一個很是重要的參數。在任務的生命週期裏,能夠經過Status屬性來獲取任務執行的狀態,當任務完成後還能夠經過任務屬性知道任務完成的狀況。可利用任務實例的Status屬性獲取任務執行的狀態,任務執行的狀態用TaskStatus枚舉表示,如下是TaskStatus的枚舉值:

Created:任務已經初始化,但還沒有進入調度計劃

WaitingForActivation:該任務已進入調度計劃,正在等待被調度程序激活

WaitingToRun:該任務已被調度程序激活,但還沒有開始執行

Running:該任務正在運行,但還沒有完成

RanToCompletion:該任務已經成功完成

Canceled:該任務因爲被取消而完成,引起異常或調用方已向該任務的CancellationToken發出信號

Faulted:該任務由於出現未經處理的異常而完成

WaitingForChildrenToComplete:該任務自己已完成,正等待附加的子任務完成

  任務完成狀況相關的屬性有IsCompleted、IsCanceled和IsFaulted等屬性,從單詞意思上看不難理解它們的意思,其中要注意IsCompleted屬性表示任務是否完成,不管是正常結束仍是由於取消或異常而完成都爲完成。

5. 任務執行的進度

  有時候咱們但願讓某些異步操做提供進度通知,以便在界面中顯示異步操做執行的進度,能夠用Progress<T>類來獲得任務執行的進度。如下是利用方法裏的Report方法將方法內變量的值傳回建立任務的事件代碼裏,從而更新進度條的值。

     CancellationTokenSource cts; private async void button3_Click(object sender, EventArgs e) { progressBar1.Maximum = 100; progressBar1.Value = 0; cts = new CancellationTokenSource(); CancellationToken ct = cts.Token; var pp = new Progress<int>(); pp.ProgressChanged += (s, n) => { progressBar1.Value = n; }; var tt = Task.Run(()=>MYThreadAsync(pp,cts.Token,500),cts.Token); try { await tt; if (tt.Exception == null) listBox1.Items.Add("任務完成"); } catch (Exception ex) { listBox1.Items.Add("異常" + ex.Message); } } private void button4_Click(object sender, EventArgs e) { cts.Cancel(); } public  void MYThreadAsync(IProgress<int> progress, CancellationToken ct, int delay) { int p = 0;//進度
            while (p < 100 && ct.IsCancellationRequested == false) { p += 1; Thread.Sleep(delay); progress.Report(p);//這個方法將會觸發ProgressChanged事件更新進度條
 } }

6. 定時完成任務

  不管是服務器仍是客戶端,都是有定時完成某個任務的須要的。System.Timers.Timer類是一個不錯的定時設置類,這個類能夠引起事件,但它默認是在線程池中引起事件,而不是在當前線程中引起事件。Timer類的經常使用屬性有AutoReset和Interval屬性,AutoReset是獲取或設置一個bool值,該值爲true表示每次間隔結束時都引起一次Elapsed事件,false表示僅在首次間隔結束時引起一次該事件。Interval屬性是獲取或設置兩次Elapsed事件的間隔時間,該值必須大於零並小於Int.MaxValue,默認值爲100毫秒。Timer類還有兩個經常使用方法那就是Start和Stop方法。

  還有一個System.Threading.Timer類,它也是在線程池中定時執行任務,它與前一個Timer類的區別是該類不使用事件模型,而是直接經過TimerCallback類型的委託來實現的。該類的構造函數爲:Timer(TimerCallback callback,Object state,TimeSpan douTime,TimeSpan period)。callback表示要執行的方法,state表示一個包含回調方法要使用的信息的對象,dueTime是首次調用回調方法以前延遲的時間,period表示每次調用回調方法的時間間隔,-1表示終止。這樣建立對象後,首次到達dueTime延時時間會自動調用一次callback委託,之後每隔period時間間隔調用一次。如下是這兩種方式的運行效果和源代碼。

 

     System.Timers.Timer timer; System.Threading.Timer threadtimer; private void button2_Click(object sender, EventArgs e)//Timers.Timer { progressBar1.Maximum = 100; progressBar1.Value = 0; int pro=0; timer = new System.Timers.Timer(500); timer.AutoReset = true; timer.Elapsed+= (obj, args) => { pro+=5; progressBar1.Value = pro; }; timer.Start(); } private void button5_Click(object sender, EventArgs e) { timer.Stop(); listBox1.Items.Add("第一個已經中止"); }     //Threading.Timer類 private void button1_Click(object sender, EventArgs e) { progressBar2.Maximum = 100; progressBar2.Value = 0; TimeSpan dueTime = new TimeSpan(0, 0, 0, 1); TimeSpan period = new TimeSpan(0, 0, 0, 0, 200); System.Threading.TimerCallback timecall = new TimerCallback((obj) => progressBar2.Value += 5); threadtimer = new System.Threading.Timer(timecall, null, dueTime, period); } private void button6_Click(object sender, EventArgs e) { threadtimer.Dispose(); listBox1.Items.Add("第二個已經中止"); }

  這篇文章只總結了單個任務的異步執行的基礎,還得繼續學習多任務並行執行。若是有更好的技術或者與企業使用相關的異步技術,但願園友能夠提出我繼續學習。

相關文章
相關標籤/搜索