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对象。








C#多线程(四),布布扣,bubuko.com

C#多线程(四)

上一篇:Java反射实现DWR技术级联查询框的封装


下一篇:python学习笔记(一)运行,输入输出,数据类型