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

C#多線程(四)

編輯:C#入門知識

一、基於事件的異步模型(Event-Based Asynchronous EBA)

EAP提供了一個簡單的方式來使用多線程編程,提供了多線程能力的同時而不用顯式的打開或者管理線程。同時提供了一個取消模型,可以安全的更新WPF和WindowsFormsControl的屬性。

EAP只是一個模型,這些特性必須要采用實際的代碼來實現。在.NET Framework中最明顯的就是BackgroundWorker類、WebClient類等。這些類中的方法以*Async結尾的犯法就是異步執行的:它們新打開一個線程,將結果返回給調用的方法,當執行完畢之後調用以*Completed結尾的方法來處理,這個方法會自動調用Invoke方法(WPF和WindowForms中)。

二、BackgroundWorker類

這是一個System.ComponentModel命名空間中的輔助類,這是一個一般意義上EAP模型的實現。這個類使用了線程池,因此不能調用Abort方法。

1、使用BackgroundWorker

首先實例化一個BackgroundWorker類的對象,並且調用DoWork方法調用RunWorkerAsync方法,可以提供一個對象類型的參數
  static BackgroundWorker _bw = new BackgroundWorker();
 
  static void Main()
  {
    _bw.DoWork += bw_DoWork;
    _bw.RunWorkerAsync ("Message to worker");
    Console.ReadLine();
  }
 
  static void bw_DoWork (object sender, DoWorkEventArgs e)
  {
    // This is called on the worker thread
    Console.WriteLine (e.Argument);        // writes "Message to worker"
    // Perform time-consuming task...
  }
RunWorkerAsync方法將參數總是傳遞給DoWork方法。同時在DoWork方法執行完畢之後,有一個RunWorkerCompleted事件,來處理執行完畢之後的動作。 支持進度報告:需要設置WorkerReportsProgress 屬性為true,同時周期性的在DoWork中調用ReportProgress 方法,然後還要處理ProgressChanged 事件,通過它的事件參數的屬性ProgressPercentage來實現。ProgressChanged事件中的代碼可以自由的與UI控件進行交互,對更新進度條尤為有用。支持退出報告:設置WorkerSupportsCancellation屬性為true;在DoWork中周期地檢查CancellationPending屬性:如果為true,就設置事件參數的Cancel屬性為true,然後返回。(工作線程可能會設置Cancel為true,並且不通過CancellationPending進行提示——如果判定工作太過困難並且它不能繼續運行);調用CancelAsync來請求退出

2、使用BackgroundWorker實現子類

這個類不是密封類,當寫一個很耗時的方法時,使用一個返回BackgroundWorker子類的方法來實現,預配置完成異步的工作,只要處理RunWorkerCompleted事件和ProgressChanged事件。

三、定時器

周期性的執行某個方法最簡單的方法就是使用一個計時器,比如System.Threading 命名空間下Timer類。線程計時器利用了線程池,允許多個計時器被創建而沒有額外的線程開銷。 Timer 算是相當簡易的類,它有一個構造器和兩個方法(這對於極簡主義者來說是最高興不過的了)。

public sealed class Timer : MarshalByRefObject, IDisposable
{
  public Timer (TimerCallback tick, object state, 1st, subsequent);
  public bool Change (1st, subsequent);   // To change the interval
  public void Dispose();                // To kill the timer
}
1st = time to the first tick in milliseconds or a TimeSpan
subsequent = subsequent intervals in milliseconds or a TimeSpan
 (use Timeout.Infinite for a one-off callback)

接下來這個例子,計時器5秒鐘之後調用了Tick 的方法,它寫"tick...",然後每秒寫一個,直到用戶敲 Enter:

using System;
using System.Threading;
  
class Program {
  static void Main() {
    Timer tmr = new Timer (Tick, "tick...", 5000, 1000);
    Console.ReadLine();
    tmr.Dispose();         // End the timer
  }
  
  static void Tick (object data) {
    // This runs on a pooled thread
    Console.WriteLine (data);          // Writes "tick..."
  }
}

.NET framework在System.Timers命名空間下提供了另一個計時器類。它完全包裝自System.Threading.Timer,在使用相同的線程池時提供了額外的便利——相同的底層引擎。下面是增加的特性的摘要:

  • 實現了Component,允許它被放置到Visual Studio設計器中
  • Interval屬性代替了Change方法
  • Elapsed 事件代替了callback委托
  • Enabled屬性開始或暫停計時器
  • 提夠Start 和 Stop方法,萬一對Enabled感到迷惑
  • AutoReset標志來指示是否循環(默認為true)

    例子:

    using System;
    using System.Timers;   // Timers namespace rather than Threading
      
    class SystemTimer {
      static void Main() {
        Timer tmr = new Timer();       // Doesn't require any args
        tmr.Interval = 500;
        tmr.Elapsed += tmr_Elapsed;    // Uses an event instead of a delegate
        tmr.Start();                   // Start the timer
        Console.ReadLine();
        tmr.Stop();                    // Pause the timer
        Console.ReadLine();
        tmr.Start();                   // Resume the timer
        Console.ReadLine();
        tmr.Dispose();                 // Permanently stop the timer
      }
      
      static void tmr_Elapsed (object sender, EventArgs e) {
        Console.WriteLine ("Tick");
      }
    }


    .NET framework 還提供了第三個計時器——在System.Windows.Forms 命名空間下。雖然類似於System.Timers.Timer 的接口,但功能特性上有根本的不同。一個Windows Forms 計時器不能使用線程池,代替為總是在最初創建它的線程上觸發 "Tick"事件。假定這是主線程——負責實例化所有Windows Forms程序中的forms和控件,計時器的事件能夠操作forms和控件而不違反線程安全——或者強加單元線程模式。Control.Invoke是不需要的。它實質上是一個單線程timer

    Windows Forms計時器必須迅速地執行來更新用戶接口。迅速地執行是非常重要的,因為Tick事件被主線程調用,如果它有停頓, 將使用戶接口變的沒有響應。

    四、 局部存儲

    每個線程與其它線程數據存儲是隔離的,這對於“不相干的區域”的存儲是有益的,它支持執行路徑的基礎結構,如通信,事務和安全令牌。 通過方法參數傳遞這些數據是十分笨拙的。存儲這些數據到靜態域意味著這些數據可以被所有線程共享。

    Thread.GetData從一個線程的隔離數據中讀,Thread.SetData 寫入數據。 兩個方法需要一個LocalDataStoreSlot對象來識別內存槽——這包裝自一個內存槽的名稱的字符串,這個名稱 你可以跨所有的線程使用,它們將得到不各自的值,看這個例子:

    class ... {
     
        // 相同的LocalDataStoreSlot 對象可以用於跨所有線程
     
      LocalDataStoreSlot secSlot = Thread.GetNamedDataSlot  ("securityLevel");
     
        // 這個屬性每個線程有不同的值
     
        int SecurityLevel {
     
        get {
            object data = Thread.GetData (secSlot);
            return data == null ? 0 : (int) data;    // null == 未初始化
           }
        set {
            Thread.SetData (secSlot, value);
            }
      }


    Thread.FreeNamedDataSlot將釋放給定的數據槽,它跨所有的線程——但只有一次,當所有相同名字LocalDataStoreSlot對象作為垃圾被回收時退出作用域時發生。這確保了線程不得到數據槽從它們的腳底下撤出——也保持了引用適當的使用之中的LocalDataStoreSlot對象。








  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved