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!";
}
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
这意味着辅助线程代码不再担心需要过多关注 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 的代码依然存在,它还占有一席之地。
//不幸的是,没有简单的方法可以完全摆脱它。
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被调用。
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代码如下:
{
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退出。
思考: 下面的代码会出现什么问题?
{
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来等待所有的任务完成:
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页面,我们按顺序取得它们,然后像下面这样比较它们的输出:
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 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页面,同时实现一个计算:
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 类型是一个系统定义的委托,用于调用不带参数的方法。
// 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 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.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命名空间下提供了另一个计时器类。它wrap了System.Threading.Timer,提供了额外的便利。下面是增加的特性的摘要:
这里是例子:
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");
}
}
出处:http://www.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。