stand on the shoulders of giants

Threading in C# - Using Thread

 1. why "[STAThread]"?

不知道你注意到没,WinForm程序一般都在main函数上有个特性[STAThread], 这是为什么呢?

先从Apartments(单元)说起,我们知道WinForm程序需要调用很多传统win32的代码,所以要适应传统的单元模式。

单元其实是包含线程和对象的容器。一个单元内的对象只能被这个单元内的线程调用。 外部单元如何调用的?例如外部如何改变UI?

需要Marshalling, 就是说需要现在外面排队,由一个中介把这些调用通知到单元内的对象。就是说排列那些对单元内对象的calls,在winows Form中,是通过message pump, this is the mechanism that constantly checks for keyboard and mouse events from the operating system. If messages arrive too quickly to be processed, they enter a message queue, so they can be processed in the order they arrive.

执行纯粹.NET代码时,Apartment不起作用.

Thread t = new Thread (...); .NET线程如果进入win32或者COM代码,会自动分配apartements,而且是多线程的。
除非设置:t.SetApartmentState (ApartmentState.STA);

造成了在一个STA状态的apartment的两个线程可以同时call same method on the same object

所以,使用[STAThread], request that the main thread join a single-threaded apartment  

否则,one of two things will occur upon reaching Win32 UI code:

  • it will marshal over to a single-threaded apartment
  • it will crash

 2. Control.Invoke()

In a multi-threaded Windows Forms application, it's illegal to call a method or property on a control from any thread other than the one that created it. 就是说:非UI线程直接调用UI元素是非法的
All cross-thread calls must be explicitly marshalled to the thread that created the control (usually the main thread),
方法是:using the Control.Invoke or Control.BeginInvoke method. 而不能依赖automatic marshalling because it takes place too late – 只有当执行到非托管代码时它才发生, by which time plenty of internal .NET code may already have run on the "wrong" thread – code which is not thread-safe.

有多种方式可以从工作线程获取消息,并将该消息传递给 UI 线程。理论上讲,可以使用低级的同步原理和池化技术来生成自己的机制,但幸运的是,因为有一个以 Control 类的 Invoke 方法形式存在的解决方案,所以不需要借助于如此低级的工作方式。

始终可以对来自任何线程的 Control 进行 Invoke 调用。Invoke 方法本身只是简单地携带委托以及可选的参数列表,并在 UI 线程中为您调用委托,而不考虑 Invoke 调用是由哪个线程发出的。但应该注意,只有在 UI 线程当前未受到阻塞时,这种机制才有效 — 调用只有在 UI 线程准备处理用户输入时才能通过。Invoke 方法会进行测试以了解调用线程是否就是 UI 线程。如果是,它就直接调用委托。否则,它将安排线程切换,并在 UI 线程上调用委托。无论是哪种情况,委托所包装的方法都会在 UI 线程中运行,并且只有当该方法完成时,Invoke 才会返回。

Control 类也支持异步版本的 Invoke,它会立即返回并安排该方法以便在将来某一时间在 UI 线程上运行。这称为 BeginInvoke,它与异步委托调用(下文会讲到)很相似。

由于 BeginInvoke 不容易造成死锁,所以尽可能多用该方法;而少用 Invoke 方法。因为 Invoke 是同步的,所以它会阻塞工作线程,就是说工作线程运行control.invoke()会block,委托传递给UI线程,并不会马上运行,要等到UI 线程可用(调用只有在 UI 线程准备处理用户输入时才能通过)。但是如果 UI 线程正在等待辅助线程执行某操作,情况会怎样呢?应用程序会死锁。BeginInvoke 从不等待UI 线程,因而可以避免这种情况。

// Created on UI thread
private Label lblStatus;
•••
// 
private void RunsOnWorkerThread() {
    DoSomethingSlow();
    lblStatus.Text 
= "Finished!";    // BAD!! worker thread 访问UI control
}


// 改进后
首先,必须将一个委托传递给 Control 的 BeginInvoke 方法,
一旦工作线程完成缓慢的工作后,它就会调用 Label 中的 BeginInvoke,以便在其 UI 线程上运行某段代码(UpdateUI)。通过这样,它可以更新用户界面。

// Created on UI thread
private Label lblStatus;
•••
// 
private void RunsOnWorkerThread() {
    DoSomethingSlow();
    
// Do UI update on UI thread    从worker thread发起,访问UI control  
    object[] pList = { this, System.EventArgs.Empty };
    lblStatus.BeginInvoke(
      
new System.EventHandler(UpdateUI), pList);
}
•••
// Code to be run back on the UI thread
// (using System.EventHandler signature
// so we don't need to define a new
// delegate type here)
private void UpdateUI(object o, System.EventArgs e) {
    
// Now OK - this method will be called via
    
// Control.Invoke, so we are allowed to do
    
// things to the UI.
    lblStatus.Text = "Finished!";
}

Wrapping Invoke, 如何ShowProgress

如果辅助线程希望在结束时提供更多的反馈信息,而不是简单地给出“Finished!”消息,需要设法向 UpdateUI 函数传递一个参数,多次调用 BeginInvoke,这样不仅会造成不便,而且考虑到辅助线程与 UI 的协调性。这样设计也不好。

ShowProgress 方法encapsulates了处理调用正确线程的工作。
这意味着辅助线程代码不再担心需要过多关注 UI 细节,而只要at regular intervals调用 ShowProgress 即可。
public class MyForm : System.Windows.Forms.Form {
        
    
public void ShowProgress(string msg, int percentDone) {
        
// Wrap the parameters in some EventArgs-derived custom class:
        System.EventArgs e = new MyProgressEvents(msg, percentDone);
        
object[] pList = { this, e };
 
        
// Invoke the method. This class is derived
        
// from Form, so we can just call BeginInvoke
        
// to get to the UI thread.
        BeginInvoke(new MyProgressEventsHandler(UpdateUI), pList);
    }
 
    
private delegate void MyProgressEventsHandler(
        
object sender, MyProgressEvents e);
 
    
private void UpdateUI(object sender, MyProgressEvents e) {
        lblStatus.Text 
= e.Msg;
        myProgressControl.Value 
= e.PercentDone;
    }
}
Control 类将公开一个称为 InvokeRequired 的属性。它可从任何线程读取,如果调用线程是 UI 线程,则返回假,其他线程则返回真。这意味着我可以按以下方式修改包装: 
public void ShowProgress(string msg, int percentDone) {
    
if (InvokeRequired) {
        
// As before
        •••
    } 
else {
        
// We're already on the UI thread just
        
// call straight through.
        UpdateUI(thisnew MyProgressEvents(msg,
            PercentDone));
    }
}
//ShowProgress 现在可以记录为可从任何线程调用的公共方法。
//这并没有消除复杂性 — 执行 BeginInvoke 的代码依然存在,它还占有一席之地。
//不幸的是,没有简单的方法可以完全摆脱它。
3. BackgroundWorker

BackgroundWorker is a helper class in the System.ComponentModel namespace for managing a worker thread. It provides the following features:

  • A "cancel" flag for signaling a worker to end without using Abort
  • A standard protocol for reporting progress, completion and cancellation
  • An implementation of IComponent allowing it be sited in the Visual Studio Designer
  • Exception handling on the worker thread
  • The ability to update Windows Forms and WPF controls in response to worker progress or completion.

最后两个特性是相当地有用:意味着你不再需要在工作线程中调用try/catch语句块了,并且更新Windows Forms控件不需要调用 Control.Invoke了。

BackgroundWorker使用线程池工作,线程池recycles threads to avoid recreating them for each new task.
所以不能call Abort on a BackgroundWorker thread.

下面是使用BackgroundWorker最少的步骤

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

这就设置好了它,任何被传入RunWorkerAsync的参数将通过事件参数的Argument属性,传到DoWork事件委托的方法中.
调用RunWorkerAsync,触发DoWork()事件, DoWork事件的委托bw_DoWork被调用。

class Program {
  
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
  }

 

BackgroundWorker也提供了RunWorkerCompleted事件,它在DoWork事件完成后触发,处理RunWorkerCompleted事件并不是强制的,但是为了查询到DoWork中的异常,你通常会这么做的。
RunWorkerCompleted Handler中的代码可以更新Windows Forms & WPF 控件,而DoWork是不行的

添加进度报告支持:

  • 设置WorkerReportsProgress属性为true
  • DoWork中使用“完成百分比”周期地调用ReportProgress方法
  • 处理ProgressChanged事件,查询它的事件参数的 ProgressPercentage属性

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

添加退出报告支持:

  • 设置WorkerSupportsCancellation属性为true
  • DoWork中周期地检查CancellationPending属性:如果为true,就设置事件参数的Cancel属性为true,然后返回。(工作线程可能会设置Cancel为true,并且不通过CancellationPending进行提示——如果判定工作太过困难并且它不能继续运行)
  • 调用CancelAsync来请求退出

带有进度报告和中途可以退出的BackgroundWorker代码如下:

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 + "%");
        }
    }

在DoWork()中按照进度调用ReportProgress(i), 触发ProcgressChanged事件,调用bw_ProgressChanged方法。
执行效果是:
Press Enter in the next 5 seconds to cancel
Reached 0%
Reached 20%
Reached 40%
Reached 60%
Reached 80%
Reached 100%
Complete - 123
//按两次Enter键,退出
如果在执行Reached 20%的时候,按下Enter键,则主线程执行bw.CancelAsync(), 则DoWork()检测到bw.CancellationPending,
就设置事件参数的Cancel属性为true,return退出。

思考: 下面的代码会出现什么问题?

public partial class Form1 : Form
    {
        BackgroundWorker bw 
= new BackgroundWorker();
            
        
public Form1()
        {
            InitializeComponent();

            bw.DoWork 
+= new DoWorkEventHandler(bw_DoWork);
            bw.RunWorkerCompleted 
+= new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);           
        }

        
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            
// change the control
            
//threadLabel.Text = "Changed by worker thread";
        }

        
void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            
//do nothing
            threadLabel.Text = "Changed by worker thread";
        }

        
private void button1_Click(object sender, EventArgs e)
        {
            bw.RunWorkerAsync();
            
//ThreadStart ts = new ThreadStart(ChangeControl);
            
//Thread t = new Thread(ts);
            
//t.Start();
            
//t.Join();
        }

        
private void ChangeControl()
        {
            threadLabel.Text 
= "Changed by worker thread";
        }

        
    }

在DoWork()中改变UI控件,会引发引发InvalidOperationException,
Cross-thread operation not valid: Control 'threadLabel' accessed from a thread other than the thread it was created on

如果不用BackgroundWorker,尝试在新线程中改变UI控件,如同上面button1_click()中注释的代码,同样会引发这样的异常。

3. Thread Pooling

适用于,如果程序有很多线程浪费时间,block在wait handle
线程池可以将多个wait Handle合并在一起
使用线程池,你需要一个将被执行的委托,并注册它的Wait Handle
这个工作通过调用ThreadPool.RegisterWaitForSingleObject来完成,如下:

class Test {
  
static ManualResetEvent starter = new ManualResetEvent (false);
 
  
public static void Main() {
    ThreadPool.RegisterWaitForSingleObject (starter, Go, 
"hello"-1true); 
   //true 委托只执行一次,因为ManualResetEvent,并不自动关门,所以false的话,委托会一直执行下去

    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
  }

所有进入线程池的线程都是后台的线程,这意味着它们在程序的前台线程终止后将自动的被终止。但你如果想在程序结束之前,确认进入线程池的线程都完成它们的重要工作,在它们上调用Join是不行的,因为进入线程池的线程从来不会结束!意思是说,它们被改为循环,直到父进程终止后才结束。所以想知道运行在线程池中的线程是否完成,你必须发信号——比如用另一个Wait Handle。

对一个线程池中的线程调用Abort是不允许的,这些线程要一直在应用程序域中循环。

你也可以用QueueUserWorkItem方法而不用Wait Handle来使用线程池,它定义了一个立即执行的委托。
惯例:线程池保持线程总数封顶(默认为25)  ThreadPool.GetMaxThreads(out a, out b); a我得到的250
It's rather like an application-wide producer-consumer queue with 25 consumers!

在下面的例子中,100个任务入列到线程池中,而一次只执行 20个,主线程使用Wait 和 Pulse来等待所有的任务完成:

class Test {
  
static object workerLocker = new object ();
  
static int runningWorkers = 100;    
 
  
public static void Main() {     
      ThreadPool.SetMaxThreads(
205);
    
for (int i = 0; i < runningWorkers; 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 (
50000);
    Console.WriteLine (
"Ended: " + instance);
    
lock (workerLocker) {
      runningWorkers
--; Monitor.Pulse (workerLocker);
    }
  }
}

执行结果:
Started: 0
Waiting for threads to complete...
Started: 1
...
Started: 19
//等待50S,然后退出一个,入队一个
Ended: 0
Started: 20
Ended: 1
Started: 21

为了给目标方法传递多余一个对象,你可以自定义拥有所有需要属性的对象,或者调用一个匿名方法。比如如果Go方法接收两个整型参数,会像下面这样:ThreadPool.QueueUserWorkItem (delegate (object notUsed) { Go (23,34); });
另一个进入线程池的方式是通过异步委托

4. Asynchronous Delegates

在第一部分我们描述如何使用 ParameterizedThreadStart把数据传入线程中。有时候你需要通过另一种方式,来从线程中得到它完成后的返回值。
异步委托提供了一个便利的机制,允许许多参数在两个方向上传递。
此外,未处理的异常在异步委托中在原始线程上被重新抛出,因此在工作线程上不需要明确的处理了。
异步委托也提供了进入线程池的另一种方式。

我们首先讨论更常见的同步模型。我们假设我们想比较两个web页面,我们按顺序取得它们,然后像下面这样比较它们的输出:

static void ComparePages() {
  WebClient wc 
= new WebClient ();
  Console.WriteLine(
"begin time: {0}", System.DateTime.Now);
  
string s1 = wc.DownloadString ("http://www.oreilly.com");
  
string s2 = wc.DownloadString ("http://oreilly.com");
  Console.WriteLine (s1 
== s2 ? "Same" : "Different");
  Console.WriteLine(
"end time: {0}", System.DateTime.Now);
}

得到的时间间隔是3秒,3:37:48 - 3:37:51
执行顺序是同步的,得到s1才会接着下载第二个页面,如果两个页面同时下载当然会更快了。 

方法一就是多线程:

static string s2 = "";
  
static string s1 = "";
  
static void ComparePages()
  {
      WebClient wc1 
= new WebClient();
 
      Thread ts 
= new Thread(delegate() { WebClient wc2 = new WebClient(); s2 = wc2.DownloadString("http://www.oreilly.com"); });
      ts.Start();
      Console.WriteLine(
"begin time: {0}", System.DateTime.Now);
      s1 
= wc1.DownloadString("http://oreilly.com");     
      
      ts.Join();

      Console.WriteLine(s1 
== s2 ? "Same" : "Different");
      Console.WriteLine(
"end time: {0}", System.DateTime.Now);
  }

得到的时间间隔是2秒,3:34:56 - 3:34:58

方法二是异步委托:
以非阻止的异步模式(non-blocking asynchronous fashion), 调用我们需要的方法,例如DownloadString, 也就是说

  1. We tell DownloadString to start executing.
  2. We perform other tasks while it's working, such as downloading another page.
  3. We ask DownloadString for its results.

WebClient类实际上提供一个被称为DownloadStringAsync的内建方法,它提供了就像异步函数的功能。而眼下,我们忽略这个问题(下面会讨论),集中精力在任何方法都可以被异步调用的机制上。

第三部使得异步委托有用,调用者可以从工作线程中得到完成后的返回值结果和任何异常。
所以这可以看作线程共享数据的另一种方法,除了静态变量和ParameterizedThreadStart,上面多线程的方法就用到了静态变量 static string s1

下面我们用异步委托来下载两个web页面,同时实现一个计算:

delegate string DownloadString(string uri);

  
static void ComparePages()
  {

      
// Instantiate delegates with DownloadString's signature:
      DownloadString download1 = new WebClient().DownloadString;
      DownloadString download2 
= new WebClient().DownloadString;


      Console.WriteLine(
"begin time: {0}", System.DateTime.Now);
      
// Start the downloads:
      IAsyncResult cookie1 = download1.BeginInvoke("http://www.oreilly.com"nullnull);
      IAsyncResult cookie2 
= download2.BeginInvoke("http://oreilly.com"nullnull);

      
// Perform some random calculation:
      
//double seed = 1.23; int i = 0;
      
//for (; 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");
      Console.WriteLine(
"end time: {0}", System.DateTime.Now);
  }

在这个例子中,我们需要两个委托,每个引用不同的WebClient的对象(WebClient 不允许并行的访问,如果它允许,我们就只需一个委托了)。
我们然后调用BeginInvoke,这开始执行并立刻返回控制器给调用者。依照我们的委托,我们必须传递一个字符串给 BeginInvoke (这个是由编译器实现的,根据委托的签名生成相应的BeginInvoke和EndInvoke)。
BeginInvoke 还需要两个参数:一个可选callback和数据对象;它们通常不需要而被设置为null, BeginInvoke返回一个 IASynchResult对象,它担当着调用 EndInvoke所用的数据。IASynchResult 同时有一个IsCompleted属性来检查进度。
之后我们在委托上调用EndInvoke ,得到需要的结果。如果有必要,EndInvoke会等待,直到方法完成,返回的值适合委托定义的一致的(string, in this case).
EndInvoke一个好的特性是你需要异步执行的方法(DownloadString,in this case)有任何的引用或输出参数,他们将会被added into EndInvoke's signature, 这样就允许一次返回多个值给caller
如果在异步委托方法执行时遇到未处理的异常,it's re-thrown on the caller's thread upon calling EndInvoke. 这个提供了简洁的机制for marshaling exceptions back to the caller.

简单理解

委托通常是以同步方式进行调用,即,在调用委托时,只有包装方法返回后该调用才会返回。
要以异步方式调用委托,请调用 BeginInvoke 方法,这样会对该方法排队以在系统线程池的线程中运行。调用线程会立即返回,而不用等待该方法完成。这比较适合于 UI 程序,因为可以用它来启动耗时较长的作业,而不会使用户界面反应变慢。
委托调用 BeginInvoke 会使该方法在系统线程池的线程中运行,而不会阻塞 UI 线程以便其可执行其他操作。该方法不返回数据,所以启动它后就不用再去管它。如果您需要该方法返回的结果,则 BeginInvoke 的返回值很重要,并且您可能不传递空参数。然而,对于大多数 UI 应用程序而言,这种“启动后就不管”的风格是最有效的,稍后会对原因进行简要讨论。您应该注意到,BeginInvoke 将返回一个 IAsyncResult。这可以和委托的 EndInvoke 方法一起使用,以在该方法调用完毕后检索调用结果(上例WebClient就是这么做的)

例如,在以下代码中,System.Windows.Forms.MethodInvoker 类型是一个系统定义的委托,用于调用不带参数的方法。

 

private void StartSomeWorkFromUIThread () {
    
// The work we want to do is too slow for the UI
    
// thread, so let's farm it out to a worker thread.

    MethodInvoker mi 
= new MethodInvoker(
        RunsOnWorkerThread);
    mi.BeginInvoke(
nullnull); // This will not block.
}

// The slow work is done here, on a thread
// from the system thread pool.
private void RunsOnWorkerThread() {
    DoSomethingSlow();
}

如果想要传递参数,可以选择合适的系统定义的委托类型,或者自己来定义委托。MethodInvoker 委托并没有什么神奇之处。

还有其他一些可用于在另外的线程上运行方法的技术,例如,直接使用线程池 API 或者创建自己的线程(和上面说的一致,三种方法)。然而,对于大多数用户界面应用程序而言,有异步委托调用就足够了。采用这种技术不仅编码容易,而且还可以避免创建并非必需的线程,因为可以利用线程池中的共享线程来提高应用程序的整体性能。 

异步方法

NET Framework 中的一些类型提供了某些它们方法的异步版本,它们使用"Begin" 和 "End"开头。它们被称之为异步方法,它们有与异步委托类似的特性,但存在着一些待解决的困难的问题:允许比你所拥有的线程还多的并发活动率。就是说异步方法常用于高并发程序。比如一个web或TCP Socket服务器,如果用NetworkStream.BeginReadNetworkStream.BeginWrite来写的话,可能在少量的线程池线程中处理数百个并发的请求。

除非你写了一个专门的高并发程序,尽管如此,你还是应该如下理由尽量避免异步方法:

  • 不像异步委托,异步方法实际上可能没有与调用者同时执行
  • 异步方法的好处被侵腐或消失了,如果你未能小心翼翼地遵从它的模式
  • 当你恰当地遵从了它的模式,事情立刻变的复杂了

如果你只是像简单地获得并行执行的结果,你最好不要用函数的同步版本,例如NetworkStream.Read。 而是使用异步委托。
或者:线程池,backgroundWorker,或者多线程。
Another option is to use ThreadPool.QueueUserWorkItem or BackgroundWorker—or simply create a new thread.

异步事件

基于"event-based asynchronous pattern", 类型可以提供异步版本的方法,例如webClient的DownloadStringAsync 方法. 这些方法以Async结尾。
调用DownloadStringAsync方法,方法执行完毕,触发Completed事件,调用你定义好的event handler

基于事件的模式也提供了报道进度和取消操作,可对Windows程序更新forms和控件。如果在某个类型中你需要这些特性,而它却不支持(或支持的不好)基于事件的模式,你没必要去自己实现它(你也根本不想去做!)。只要用通过 BackgroundWorker这个帮助类便可轻松完成。

计时器

周期性的执行某个方法最简单的方法就是使用一个计时器,比如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"50001000);
    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命名空间下提供了另一个计时器类。它wrap了System.Threading.Timer,提供了额外的便利。下面是增加的特性的摘要:
  • 实现了Component,允许它被放置到Visual Studio设计器中 
  • Interval属性代替了Change方法
  • Elapsed 事件代替了callback委托
  • Enabled属性开始或暂停计时器
  • 提够StartStop方法,万一对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");
      }
    }

     

  • posted @ 2009-06-19 17:04  DylanWind  阅读(4158)  评论(0编辑  收藏  举报