您的当前位置:首页正文

Threading in C# - Using Thread

2021-09-25 来源:钮旅网


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(this, new 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

gner

An implementation of IComponent allowing it be sited in the Visual Studio Desi

 Exception handling on the worker thread

 The ability to update Windows Forms and WPF controls in response to worker p

rogress 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,就设置事件参数的Canc

el属性为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\", -1, true);

//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(20, 5);

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, 也就是说

 We tell DownloadString to start executing.

 We perform other tasks while it's working, such as downloading another page.

 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\", null, null);

IAsyncResult cookie2 = download2.BeginInvoke(\"http://oreilly.com\", null, null);

// 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(null, null); // 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.BeginRead 和 NetworkStream.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 Console.ReadLine();

tmr.Dispose(); // End the timer

\", 5000, 1000);

}

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属性开始或暂停计时器

 提够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\"); } }

因篇幅问题不能全部显示,请点此查看更多更全内容