C#中的线程(三) 使用多线程

其三有:使用多线程

 

1.  单元模式和Windows Forms

       单元模式线程是一个自行线程安全机制,
非常守于COM——Microsoft的遗留下的组件对象模型。尽管.NET最酷地放弃摆脱了遗留下的模型,但多下她呢会忽然冒出,这是以有必要和老的API
进行通信。单元模式线程与Windows Forms最相关,因为大多Windows
Forms使用或者包了长期存在的Win32 API——连同它的单元传统。

      
单元是多线程的逻辑上之“容器”,单元有两种植容量——“单之”和“多之”。单线
程单元只含有一个线程;多线程单元可以蕴涵其他数据之线程。单线程模式再度普遍
并且能及彼此发生互操作性。

      
就比如包含线程一样,单元为富含对象,当目标在一个单元内被创造后,在其的生命周期中它用一直是于那,永远为“居家不起”地与那些驻留线程在共同。这仿佛于给含有在.NET
同步环境中
,除了同环境遭到尚无好的或者包含线程。任何线程可以看于其它共同环境受到之靶子
——在拔除它锁的决定着。但是单元内之目标只有单元内的线程才堪看。

      
想象一个图书馆,每本书还意味着一个靶;借发书是无叫允许的,书还当图书馆
创建并直到其了。此外,我们之所以一个人数来表示一个线程。

      
一个联机内容的图书馆同意任何人进来,同时同一时刻才同意一个丁登,在图书馆外会形成队列。

      
单元模式的图书馆有常驻维护人员——对于单线程模式之图书馆来一个书管理员,
对于多线程模式之图书馆则发出一个团的管理员。没人于允许除了隶属与保安人员之人
——资助人怀念使就研究就得为图书管理员发信号,然后报组织者去做工作!给管理员发信号于称为调度编组——资助人经过调度把法依次读来受一个专属管理员的人(或,某个隶属管理员的口!)。
调度编组是活动的,在Windows
Forms通过信息泵被实现在库结尾。这即是操作系统经常检查键盘与鼠标的体制。如果消息达的最抢了,以致不可知给拍卖,它们以形成消息队列,所以它们得以坐它们到达的逐一为拍卖。

 

1.1  定义单元模式

 

       
.NET线程在进单元核心Win32或原的COM代码前自行地叫单元赋值,它叫默认地指定为多线程单元模式,除非需要一个单线程单元模式,就比如下的平:

?

1
2
Thread t = new Thread (...);
t.SetApartmentState (ApartmentState.STA);

        你也可以为此STAThread特性标在主线程上来深受它们跟单线程单元相结合:

?

1
2
3
4
class Program {
  [STAThread]
static void Main() {
  ...

        线程单元设置对纯.NET代码没有力量,换言之,即使少单线程都生STA
的单元状态,也得以于同样之靶子又调用相同的法门,就从来不电动的信号编组或锁定发生了,
只有当实行非托管的代码时,这才会生出。

在System.Windows.Forms名称空间下的型,广泛地调用Win32代码,
在单线程单元下办事。由于斯缘故,一个Windos
Forms程序应该当它们的主方法及粘贴上 [STAThread]特征,除非在推行Win32
UI代码之前以下二者有来了:

  • 其以调度编组成一个单线程单元
  • 它用崩溃

 

1.2  Control.Invoke

 

以多线程的Windows
Forms程序中,通过非创建控件的线程调用控件的之性能和艺术是伪的。所有跨进程的调用必须被明显地排列至创建控件的线程中(通常也主线程),利用Control.Invoke

Control.BeginInvoke方法。你切莫能够拄自动调度编组为它们有的最晚了,仅当行刚进入了非托管的代码它才来,而.NET已发出足够的日子来运行“错误的”线程代码,那些非线程安全的代码。

一个可以之田间管理Windows Forms程序的方案是利用BackgroundWorker,
这个近乎包装了用报道快与成功过的做事线程,并活动地调用Control.Invoke方法作为需要。

 

1.3  BackgroundWorker

 

BackgroundWorker是一个以System.ComponentModel命名空间
下帮扶类似,它管理在工作线程。它提供了以下特点:

  • “cancel” 标记,对于被工作线程打信号于她了而没下
    Abort的情况
  • 提供报道快,完成度和剥离的正式方案
  • 兑现了IComponent接口,允许其与Visual Studio设计器
  • 当劳作线程之上做深处理
  • 更新Windows Forms控件以回应工作进度或完成度的力量

     最后两个性状是一对一地中:意味着你不再需要用try/catch语句块放到
你的劳作线程中了,并且更新Windows Forms控件不待调用
Control.Invoke了。BackgroundWorker使用线程池工作,
对于每个新职责,它循环利用避免线程们收获休养生息。这意味你不可知当
BackgroundWorker线程上调用
Abort了。

     下面是采用BackgroundWorker最少的步骤:

  • 实例化 BackgroundWorker,为DoWork事件增加委托。
  • 调用RunWorkerAsync方法,使用一个不论是的object参数。

    
这就安装好了其,任何被传到RunWorkerAsync的参数将由此波参数的Argument属性,传到DoWork事件委托的方法吃,下面是例证:

?

1
2
3
4
5
6
7
8
9
10
11
12
class Program {
s   tatic 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) {
// 这被工作线程调用
    Console.WriteLine (e.Argument);        // 写"Message to worker"
    // 执行耗时的任务...
  }

     
BackgroundWorker也提供了RunWorkerCompleted事件,它于DoWork事件做到后点,处理RunWorkerCompleted事件并无是挟持的,但是为了查询及DoWork中的大,你平凡会这样做的。RunWorkerCompleted中之代码可以创新Windows
Forms 控件,而不用示的信号编组,而DoWork中即使得这样做。

丰富进程告知支持:

  • 设置WorkerReportsProgress属性为true
  • 每当DoWork中运用“完成百分比”周期地调用ReportProgress方法,以及可选用户状态对象
  • 处理ProgressChanged事件,查询其的风波参数的 ProgressPercentage属性

     
ProgressChanged中的代码就像RunWorkerCompleted一样可擅自地跟UI控件进行交互,这在更性进度栏尤为有用。

添加退出报告支持:

  • 设置WorkerSupportsCancellation属性为true
  • 于DoWork中周期地反省CancellationPending属性:如果也true,就安装事件参数的Cancel属性为true,然后返回。(工作线程可能会见装Cancel为true,并且不通过CancellationPending进行提醒——如果判断工作最过窘迫又它们不克延续运行)
  • 调用CancelAsync来呼吁退出

下的例子实现了点描述的性状:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System;
using System.Threading;
using System.ComponentModel;
  
class Program {
  static BackgroundWorker bw;
  static void Main() {
    bw = new BackgroundWorker();
    bw.WorkerReportsProgress = true;
    bw.WorkerSupportsCancellation = true;
 
    bw.DoWork += bw_DoWork;
    bw.ProgressChanged += bw_ProgressChanged;
    bw.RunWorkerCompleted += bw_RunWorkerCompleted;
  
    bw.RunWorkerAsync ("Hello to worker");
     
    Console.WriteLine ("Press Enter in the next 5 seconds to cancel");
    Console.ReadLine();
    if (bw.IsBusy) bw.CancelAsync();
    Console.ReadLine();
  }
  
  static void bw_DoWork (object sender, DoWorkEventArgs e) {
    for (int i = 0; i <= 100; i += 20) {
      if (bw.CancellationPending) {
        e.Cancel = true;
        return;
      }
      bw.ReportProgress (i);
      Thread.Sleep (1000);
    }
    e.Result = 123;    // This gets passed to RunWorkerCompleted
  }
  
  static void bw_RunWorkerCompleted (object sender,
  RunWorkerCompletedEventArgs e) {
    if (e.Cancelled)
      Console.WriteLine ("You cancelled!");
    else if (e.Error != null)
      Console.WriteLine ("Worker exception: " + e.Error.ToString());
    else
      Console.WriteLine ("Complete - " + e.Result);      // from DoWork
  }
  
  static void bw_ProgressChanged (object sender,
  ProgressChangedEventArgs e) {
    Console.WriteLine ("Reached " + e.ProgressPercentage + "%");
  }
}

SQL Server 1

 

1.4  BackgroundWorker的子类

  

      
BackgroundWorker不是密封类,它提供OnDoWork为虚方法,暗示着另外一个模式可它。
当写一个可能耗时的计,你可要顶好写个返回BackgroundWorker子类的齐措施,预配置完成异步的做事。使用者要处理RunWorkerCompleted事件以及ProgressChanged事件。比如,设想我们描绘一个耗时
的方法叫做GetFinancialTotals:

?

1
2
3
4
5
public class Client {
  Dictionary <string,int> GetFinancialTotals (int foo, int bar) { ... }
  ...
 
}

      我们得以这么来兑现:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Client {
  public FinancialWorker GetFinancialTotalsBackground (int foo, int bar) {
    return new FinancialWorker (foo, bar);
  }
}
  
public class FinancialWorker : BackgroundWorker {
  public Dictionary <string,int> Result;   // We can add typed fields.
  public volatile int Foo, Bar;            // We could even expose them
                                           // via properties with locks!
  public FinancialWorker() {
    WorkerReportsProgress = true;
    WorkerSupportsCancellation = true;
  }
  
  public FinancialWorker (int foo, int bar) : this() {
    this.Foo = foo; this.Bar = bar;
  }
  
  protected override void OnDoWork (DoWorkEventArgs e) {
    ReportProgress (0, "Working hard on this report...");
    Initialize financial report data
  
    while (!finished report ) {
      if (CancellationPending) {
        e.Cancel = true;
        return;
      }
      Perform another calculation step
      ReportProgress (percentCompleteCalc, "Getting there...");
    }     
    ReportProgress (100, "Done!");
    e.Result = Result = completed report data;
  }
}

   

     
无论谁调用GetFinancialTotalsBackground都见面获得一个FinancialWorker——一个就此实际地可用地卷入了保管后台操作。它好告诉进度,被注销,与Windows
Forms交互而未用利用Control.Invoke。它也生深句柄,并且用了正规化的合计(与用BackgroundWorker没任何区别!)

     这种BackgroundWorker的用法有效地躲开了原始片“基于事件之异步模式”。

 

2  ReaderWriterLockSlim类

 

     //注意还有一个一味的ReaderWriterLock类,Slim类为.net
3.5剧增,提高了性能。

    
通常来讲,一个类的实例对于彼此的念操作是线程安全的,但是彼此地换代操作则非是(并行地读与更新为不是)。
这对资源(比如一个文本)也是一模一样的。使用一个简易的独占锁来锁定所有或的访问能够化解实例的线程安全与否问题,但是当起多的朗读操作而一味是偶尔的换代操作的时段,这即怪不客观的范围了出现。一个例就是是就在一个事情程序服务器中,为了迅速搜索把数据缓存到静态字段中。在这样的情状下,ReaderWriterLockSlim类被设计改为提供最好充分或的锁定。

     ReaderWriterLockSlim有有限种基本的Lock方法:一个揽的Wirte Lock
,和一个及另Read lock相容的宣读锁定。

     所以,当一个线程拥有一个Write
Lock的当儿,会死所有其他线程获得读写锁。但是当没有线程获得WriteLock时,可以有多单线程同时得到ReadLock,进行读操作。

     ReaderWriterLockSlim提供了底四个方式来博与放读写锁:

?

1
2
3
4
public void EnterReadLock();
public void ExitReadLock();
public void EnterWriteLock();
public void ExitWriteLock();

 

    
另外对所有的EnterXXX方法,还有”Try”版本的艺术,它们收到timeOut参数,就如Monitor.TryEnter相同(在资源争用严重的时过发生一定好)。另外ReaderWriterLock提供了其余类的AcquireXXX
和 ReleaseXXX方法,它们超时退出的时段丢来怪而不是归false。

      
下面的次序展示了ReaderWriterLockSlim——三只线程循环地枚举一个List,同时另外两个线程每一样秒钟添加一个随意数届List中。一个read
lock保护List的读取线程,同时一个write lock保护写线程。

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class SlimDemo
{
  static ReaderWriterLockSlim rw = new ReaderWriterLockSlim();
  static List<int> items = new List<int>();
  static Random rand = new Random();
 
  static void Main()
  {
    new Thread (Read).Start();
    new Thread (Read).Start();
    new Thread (Read).Start();
 
    new Thread (Write).Start ("A");
    new Thread (Write).Start ("B");
  }
 
  static void Read()
  {
    while (true)
    {
      rw.EnterReadLock();
      foreach (int i in items) Thread.Sleep (10);
      rw.ExitReadLock();
    }
  }
 
  static void Write (object threadID)
  {
    while (true)
    {              
      int newNumber = GetRandNum (100);
      rw.EnterWriteLock();
      items.Add (newNumber);
      rw.ExitWriteLock();
      Console.WriteLine ("Thread " + threadID + " added " + newNumber);
      Thread.Sleep (100);
    }
  }
 
  static int GetRandNum (int max) { lock (rand) return rand.Next (max); }
}
<em><span style="font-family: YaHei Consolas Hybrid;">//在实际的代码中添加try/finally,保证异常情况写lock也会被释放。</span></em>

结果为:

Thread B added 61
Thread A added 83
Thread B added 55
Thread A added 33
...

     
ReaderWriterLockSlim比简单的Lock允许再次不行的连发读能力。我们能够添加一行代码到Write方法,在While循环的发端:

?

1
Console.WriteLine (rw.CurrentReadCount + " concurrent readers");

       基本上总是会回到“3 concurrent
readers”(读方法花费了重复多之光阴在Foreach循环),ReaderWriterLockSlim还提供了诸多和CurrentReadCount属性类似之习性来监视lock的情景:

?

1
2
3
4
5
6
7
8
9
10
11
public bool IsReadLockHeld            { get; }
public bool IsUpgradeableReadLockHeld { get; }
public bool IsWriteLockHeld           { get; }
 
public int  WaitingReadCount          { get; }
public int  WaitingUpgradeCount       { get; }
public int  WaitingWriteCount         { get; }
 
public int  RecursiveReadCount        { get; }
public int  RecursiveUpgradeCount     { get; }
public int  RecursiveWriteCount       { get; }

     
有时候,在一个原子操作间交换读写锁是好管用的,比如,当有item不以list中之早晚,添加这个item进去。最好的情形是,最小化写如锁的时刻,例如像下这样处理:

    1
获得一个念取锁

    2
测试list是否带有item,如果是,则赶回

    3
释放读取锁

    4
获得一个状入锁

    5
写副item到list中,释放写副锁。

     但是以步骤3、4内,当另外一个线程可能偷偷修改List(比如说添加相同一个Item),ReaderWriterLockSlim通过提供第三种植锁来解决这个题材,这就是是upgradeable
lock。一个可是升级锁和read lock
类似,只是它能够通过一个原子操作,被升级也write
lock。使用办法如下:

  1.  
    1. 调用 EnterUpgradeableReadLock
    2. 读操作(e.g. test if item already present in list)
    3. 调用 EnterWriteLock (this converts the upgradeable lock to
      a write lock)
    4. 写操作(e.g. add item to list)
    5. 调用ExitWriteLock (this converts the write lock back to an
      upgradeable lock)
    6. 另外读取的过程
    7. 调用ExitUpgradeableReadLock

     
从调用者的角度,这特别怀念递归(嵌套)锁。实际上第三步之时段,通过一个原子操作,释放了read
lock 并得到了一个初的write lock.

      upgradeable locks 和read
locks之间另外还有一个重要的分别,尽管一个upgradeable locks
能够和擅自多只read locks共存,但是一个时时,只能发出一个upgradeable
lock自己为以。这防止了死锁。这和SQL Server的Update lock类似

SQL Server 2

      我们可变更前面例子的Write方法来显示upgradeable lock:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while (true)
{
  int newNumber = GetRandNum (100);
  rw.EnterUpgradeableReadLock();
  if (!items.Contains (newNumber))
  {
    rw.EnterWriteLock();
    items.Add (newNumber);
    rw.ExitWriteLock();
    Console.WriteLine ("Thread " + threadID + " added " + newNumber);
  }
  rw.ExitUpgradeableReadLock();
  Thread.Sleep (100);
}

ReaderWriterLock 没有供upgradeable
locks的职能。

 

2.1  递归锁 Lock recursion

Ordinarily, nested or recursive locking is prohibited with
ReaderWriterLockSlim. Hence, the following throws an exception:

默认情况下,递归(嵌入)锁被ReaderWriterLockSlim禁止,因为下面的代码可能扔来好。

?

1
2
3
4
5
var rw = new ReaderWriterLockSlim();
rw.EnterReadLock();
rw.EnterReadLock();
rw.ExitReadLock();
rw.ExitReadLock();

然而来得地宣称允许嵌套的话,就能健康办事,不过就带来了无必要之复杂性。

?

1
var rw = new ReaderWriterLockSlim (LockRecursionPolicy.SupportsRecursion);

 

?

1
2
3
4
5
6
rw.EnterWriteLock();
rw.EnterReadLock();
Console.WriteLine (rw.IsReadLockHeld);     // True
Console.WriteLine (rw.IsWriteLockHeld);    // True
rw.ExitReadLock();
rw.ExitWriteLock();

   使用锁的依次大致为:Read Lock  –> 
Upgradeable Lock  –>  Write Lock

 

3   线程池

 

       
如果你的次第来诸多线程,导致消费了多光阴在等句柄的遏止上,你得经
线程池来压缩负担。线程池通过集合很多待句柄在充分少之线程上来节省时间。

        使用线程池,你要登记一个会同将为执行之寄托的Wait
Handle,在Wait
Handle发信号时。这个工作通过调用ThreadPool.RegisterWaitForSingleObject来形成,如下:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test {
  static ManualResetEvent starter = new ManualResetEvent (false);
  
  public static void Main() {
    ThreadPool.RegisterWaitForSingleObject (starter, Go, "hello", -1, true);
    Thread.Sleep (5000);
    Console.WriteLine ("Signaling worker...");
    starter.Set();
    Console.ReadLine();
  }
  
  public static void Go (object data, bool timedOut) {
    Console.WriteLine ("Started " + data);
    // Perform task...
  }
}

SQL Server 3

     
除了等候句柄和嘱托之外,RegisterWaitForSingleObject也接到一个“黑盒”对象,它让传送到您的托方被(
就像用ParameterizedThreadStart一律),拥有一个毫秒级的晚点参数(-1象征没有过)和布尔表明来指明要是一次性的或者循环的。

       所有上线程池的线程都是后台的线程,这意味
它们当次的前台线程终止后将机关的被停。但若要是想等上线程池的线程都形成她的第一工作于剥离程序之前,在它们上调用Join是雅的,因为进入线程池的线程从来不会了!意思是说,它们让转吧循环,直到父进程终止后才结束。所以啊明运行在线程池中的线程是否好,你要发信号——比如用另外一个Wait
Handle。

      在线程池中之线程上调用Abort
是一个百般主意,线程需要以程序域的生命周期中循环。

     
你呢得以据此QueueUserWorkItem方法要不用等句柄来使用线程池,它定义了一个立即施行的信托。你不用在差不多只任务中省共享线程,但发生一个老:线程池保持一个线程总数的封顶(默认为25),在职责数达到这个顶值后以机关排队。这就比如程序范围的出25独顾客的生产者/消费者队列。在底下的例子中,100只任务入列到线程池中,而同一涂鸦才实行
25个,主线程使用WaitPulse来等所有的任务成功:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Test {
  static object workerLocker = new object ();
  static int runningWorkers = 100;
  
  public static void Main() {
    for (int i = 0; i < 100; i++) {
      ThreadPool.QueueUserWorkItem (Go, i);
    }
    Console.WriteLine ("Waiting for threads to complete...");
    lock (workerLocker) {
      while (runningWorkers > 0) Monitor.Wait (workerLocker);
    }
    Console.WriteLine ("Complete!");
    Console.ReadLine();
  }
  
  public static void Go (object instance) {
    Console.WriteLine ("Started: " + instance);
    Thread.Sleep (1000);
    Console.WriteLine ("Ended: " + instance);
    lock (workerLocker) {
      runningWorkers--; Monitor.Pulse (workerLocker);
    }
  }
}

    
为了传递多个对象吃目标措施,你可定义个颇具富有需要属性之自定义对象,或者调用一个匿名方式。比如使Go方法接收两个整型参数,会如下这样:

?

1
ThreadPool.QueueUserWorkItem (delegate (object notUsed) { Go (23,34); });

另外一个进入线程池的不二法门是由此异步委托。

 

4.   异步委托

 

     在首先局部咱叙如何下
ParameterizedThreadStart把数据传线程中。有时候
你要通过其它一样种艺术,来打线程中获得其做到后底回到值。异步委托提供了一个利的建制,允许多参数在有限只样子及传递
。此外,未处理的异常在异步委托中以原始线程上被还抛出,因此于劳作线程上未需明白的拍卖了。异步委托为提供了计入
线程池的其它一样种方式。

    
对之公必须付出的代价是如果和从异步模型。为了看这象征什么,我们率先谈谈再宽广的同模型。我们要我们想比较
两个web页面,我们随顺序取得她,然后如下这样于它们的出口:

?

1
2
3
4
5
6
static void ComparePages() {
  WebClient wc = new WebClient ();
  string s1 = wc.DownloadString ("http://www.oreilly.com");
  string s2 = wc.DownloadString ("http://oreilly.com");
  Console.WriteLine (s1 == s2 ? "Same" : "Different");
}

   
如果简单单页面同时下载当然会更快了。问题在于当页面在下载时DownloadString阻止了继承调用方法。如果我们会
调用 DownloadString在一个非阻止的异步方式吃会变换的再次好,换言之:

  1. 我们报告 DownloadString 开始执行

  2. 于它执行时我们实践另外任务,比如说下载另一个页面

  3. 咱俩询问DownloadString的富有结果

    WebClient类实际上提供一个为称为DownloadStringAsync的内建方法
,它提供了便如异步函数的效应。而眼下,我们忽略这个题材,集中精力在其他方法还得以给异步调用的建制及。

   
第三步要异步委托变的管事。调用者汇集了劳作线程得到结果与兴其他异常被重复抛出。没有当即步,我们惟有普通多线程。虽然为或并非汇集方式使用异步委托,你可以为此ThreadPool.QueueWorkerItem 或 BackgroundWorker。

    下面我们因此异步委托来下充斥两单web页面,同时落实一个计:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
delegate string DownloadString (string uri);
  
static void ComparePages() {
  
  // Instantiate delegates with DownloadString's signature:
  DownloadString download1 = new WebClient().DownloadString;
  DownloadString download2 = new WebClient().DownloadString;
   
  // Start the downloads:
  IAsyncResult cookie1 = download1.BeginInvoke (uri1, null, null);
  IAsyncResult cookie2 = download2.BeginInvoke (uri2, null, null);
   
  // Perform some random calculation:
  double seed = 1.23;
  for (int i = 0; i < 1000000; i++) seed = Math.Sqrt (seed + 1000);
   
  // Get the results of the downloads, waiting for completion if necessary.
  // Here's where any exceptions will be thrown:
  string s1 = download1.EndInvoke (cookie1);
  string s2 = download2.EndInvoke (cookie2);
   
  Console.WriteLine (s1 == s2 ? "Same" : "Different");
}

    
我们为宣称和实例化我们怀念如果异步运行的办法开始。在斯事例中,我们用少个委托,每个援不同之WebClient的靶子(WebClient
不允许并行的访,如果她同意,我们便不过需要一个寄托了)。

     
我们接下来调用BeginInvoke,这起履行并马上回到控制器给调用者。依照我们的信托,我们得传递一个字符串给
BeginInvoke (编译器由生产BeginInvoke 和
EndInvoke在信托项目强迫实现者).

       BeginInvoke
还需要少单参数:一个可选callback和多少对象;它们通常不欲而为设置为null,
BeginInvoke返回一个 IASynchResult对象,它当在调用
EndInvoke所用的数额。IASynchResult 同时产生一个IsCompleted属性来检查进度。

   SQL Server    之后咱们于寄上调用EndInvoke
,得到需要的结果。如果起必不可少,EndInvoke会等待,
直到方式好,然后返回方法返回的值当委托指定的(这里是字符串)。
EndInvoke一个吓的特性是DownloadString有任何的援或输出参数, 它们会在
EndInvoke结构赋值,允许通过调用者多只价为归。

       
在异步方法的执行着之别触及出了无处理的死,它见面重新以调用线程在EndInvoke中丢掉来。
这提供了简洁的艺术来保管返回给调用者的死去活来。

        假使你异步调用的法子无回来值,你呢(理论及)应该调用EndInvoke,在一部分意义及
在开放了误判;MSDN上反驳着这个话题。如果您挑选未调用EndInvoke,你需要考虑当做事方法吃的充分。

4.1  异步方法 

      .NET Framework
中的有的档提供了一点其方法的异步版本,它们采取”Begin” 和
“End”开头。它们为称作异步方法,它们有和异步委托类似的风味,但异步委托有着有些内需解决的窘迫的题材:允许比你所兼有的线程还多的出现活动率。
比如一个web或TCP Socket服务器,如果因此NetworkStream.BeginRead  和
NetworkStream.BeginWrite
来写的言辞,就可能当仅线程池线程中处理数百个冒出的伸手。

     
除非您正写一个特地的强并发程序,否则不该多多地以异步方法。理由如下:

  • 勿像异步委托,异步方法其实可能没与调用者同时实行
  • 万一你不能审慎地遵循其的模式异步方法的益处被侵腐或消亡了,
  • 当您正当地遵从了它的模式,事情马上变的繁杂了

    
如果您只有是诸如简单地取得并行执行的结果,你最好好远离调用异步版本的艺术(比如NetworkStream.Read)
而透过异步委托。另一个选是使用ThreadPool.QueueUserWorkItem或BackgroundWorker,又要只是简单地开创新的线程。

 

4.2   异步事件

 

     
另一样种模式存在,就是胡类型可以供异步版本的道。这就算是所谓的“基于事件之异步模式”,这些的措施为”Async”结束,相对应之风波因”Completed”结束。WebClient使用此模式在其的DownloadStringAsync
方法中。 为了采取它们,你如果首先处理”Completed”
事件(例如:DownloadStringCompleted),然后调用”Async”方法(例如:DownloadStringAsync)。当方法好后,它调用你事件句柄。不幸的凡,WebClient的实现是有弱点的:像DownloadStringAsync
这样的章程对于下载的如出一辙组成部分时刻阻止了调用者的线程。

    
基于事件的模式吧提供了通讯快与撤回操作,被自己地设计改为可针对Windows程序可更新forms和控件。如果在有项目受到君要这些特性
,而她也无支持(或支撑的不得了)基于事件的模式,你没有必要去自己实现它(你吧从未思量去举行!)。尽管如此,所有的这些通过
BackgroundWorker这个帮忙类似即只是轻松做到。

 

5. 计时器

 

    
周期性的实践某方法极其简单易行的艺术就是行使一个计时器,比如System.Threading
命名空间下Timer类。线程计时器利用了线程池,允许多只计时器被创造而没有额外的线程开销。
Timer
算是一定简单的接近,它起一个构造器和片单点子(这对极简主义者来说是高兴无了的了)。

?

1
2
3
4
5
6
7
8
9
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:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

例子:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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事件被主线程调用,如果她起抛锚,
将如用户接口变的没有响应。

 

6. 有的存储

 

     
每个线程与其它线程数据存储是割裂的,这对于“不相干的区域”的贮存是便民之,它支持实施路径的基本功结构,如通信,事务以及安康令牌。
通过措施参数传递这些数据是充分傻的。存储这些多少到静态域意味着这些多少可为抱有线程共享。

      Thread.GetData自从一个线程的割裂数据中读,Thread.SetData
写副数据。
两单法子要一个LocalDataStoreSlot对象来分辨内存槽——这包自一个外存槽的称谓的字符串,这个名称
你可以跨所有的线程使用,它们将获不各自的价值,看这例子:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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对象。

相关文章