C#之使用线程池
简述
创建线程是昂贵的操作,所以为每个短暂的异步操作创建线程会产生显著的开销,线程池就是该问题的解决方案,我们事先分配一定的资源,将这些资源放入资源池,每次需要新的资源,只需从池中获取一个,而不用创建一个新的。当该资源不再被使用时,就就将其返回池中。
ThreadPool
类型拥有一个QueueUserWorkItem
静态方法。该静态方法接受一个委托,代表哦用户自定义的一个异步操作。在该方法被调用后,委托会进入到内部队列中,如果池中没有任何线程,将创建一个新的工作者线程(worker thread),并将队列中第一个委托放入到该工作者线程中。如果在线程池中放入新的操作,当目前的所有操作完成后,很可能只需要重用一个线程来执行这些新的操作,如果放置的新的操作过快,线程池将创建更多的线程来执行这些操作。创建太多的线程是有限制的,在这种情况下新的操作将在队列中等待直到线程池中的工作者线程有能力执行它们。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program1
{
private delegate string RunOnThreadPool(out int threadId);
private static void Callback(IAsyncResult ar)
{
Console.WriteLine("Starting a callback....");
Console.WriteLine($"State passed tt a callback:{ar.AsyncState}");
Console.WriteLine($"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}");
Console.WriteLine($"Thread pool worker thread id:{Thread.CurrentThread.ManagedThreadId}");
}
private static string Test(out int threadId)
{
Console.WriteLine("Starting...");
Console.WriteLine($"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}");
Thread.Sleep(TimeSpan.FromSeconds(2));
threadId = Thread.CurrentThread.ManagedThreadId;
return $"Thread pool worker thread id was:{threadId}";
}
public static void Main()
{
int threadId = 0;
RunOnThreadPool poolDelegate = Test;
var t = new Thread(() => Test(out threadId));
t.Start();
t.Join();
Console.WriteLine($"Thread id:{threadId}");
IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "a delegate asynchronous call");
r.AsyncWaitHandle.WaitOne();
string result = poolDelegate.EndInvoke(out threadId, r);
Console.WriteLine($"Thread pool worker thread id:{threadId}");
Console.WriteLine(result);
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
}
output:
Starting...
Is thread pool thread:False
Thread id:3
Starting...
Is thread pool thread:True
Thread pool worker thread id:4
Thread pool worker thread id was:4
Starting a callback....
State passed tt a callback:a delegate asynchronous call
Is thread pool thread:True
Thread pool worker thread id:4
向线程池中放入异步操作
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program2
{
private static void AsyncOperation(object state)
{
Console.WriteLine($"Operation state:{state ?? "(null)"}");
Console.WriteLine($"Worker thread id:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(TimeSpan.FromSeconds(2));
}
public static void Main()
{
const int x = 1;
const int y = 2;
const string lambdaState = "lambda state 2";
ThreadPool.QueueUserWorkItem(AsyncOperation);
Thread.Sleep(TimeSpan.FromSeconds(1));
ThreadPool.QueueUserWorkItem(AsyncOperation, "async state");
Thread.Sleep(TimeSpan.FromSeconds(1));
ThreadPool.QueueUserWorkItem(state =>
{
Console.WriteLine($"Operation state:{state}");
Console.WriteLine($"Worker thread id:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(TimeSpan.FromSeconds(2));
},"lambda state");
ThreadPool.QueueUserWorkItem(_ =>
{
Console.WriteLine($"Operattion statte:{x + y},{lambdaState}");
Console.WriteLine($"Worker thread id:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(TimeSpan.FromSeconds(2));
}, "lambda state");
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
}
output
Operation state:(null)
Worker thread id:3
Operation state:async state
Worker thread id:4
Operation state:lambda state
Worker thread id:5
Operattion statte:3,lambda state 2
Worker thread id:3
线程池与并行度
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program3
{
static void UseThreads(int numberOfOperations)
{
using(var countDown=new CountdownEvent(numberOfOperations))
{
Console.WriteLine("Scheduling work by creating thhreads");
for(int i = 0; i < numberOfOperations; i++)
{
var thread = new Thread(() =>
{
Console.Write($"{Thread.CurrentThread.ManagedThreadId},");
Thread.Sleep(TimeSpan.FromSeconds(0.1));
countDown.Signal();
});
thread.Start();
}
countDown.Wait();
}
Console.WriteLine();
}
static void UseThreadPool(int numberOfOperations)
{
using(var countDown=new CountdownEvent(numberOfOperations))
{
Console.WriteLine("Starting work on a threadPool");
for(int i = 0; i < numberOfOperations; i++)
{
ThreadPool.QueueUserWorkItem(_ =>
{
Console.Write($"{Thread.CurrentThread.ManagedThreadId},");
Thread.Sleep(TimeSpan.FromSeconds(0.1));
countDown.Signal();
});
}
countDown.Wait();
Console.WriteLine();
}
}
public static void Main()
{
const int numberOfOperations = 500;
var sw = new Stopwatch();
sw.Start();
UseThreads(numberOfOperations);
sw.Stop();
Console.WriteLine($"Execution time using threads:{sw.ElapsedMilliseconds}");
sw.Reset();
sw.Start();
UseThreadPool(numberOfOperations);
sw.Stop();
Console.WriteLine($"Execution time using the thread pool:{sw.ElapsedMilliseconds}");
}
}
}
output:
Scheduling work by creating thhreads
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,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,420,419,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,
Execution time using threads:224
Starting work on a threadPool
503,504,506,507,509,505,510,508,505,510,506,504,508,509,503,507,508,505,504,506,509,503,510,507,504,509,503,508,510,506,507,505,508,510,506,504,503,507,509,505,503,504,506,507,509,505,508,510,504,509,508,505,503,506,507,510,508,507,504,510,511,506,509,505,503,506,504,511,505,507,510,503,508,509,504,510,505,503,506,507,509,511,508,508,506,505,507,503,511,504,510,509,506,503,504,505,508,509,507,510,511,510,504,507,509,503,505,508,506,511,508,507,509,510,505,504,506,503,511,506,503,504,505,508,509,507,510,511,510,509,505,503,506,508,507,504,511,508,506,504,507,509,511,503,505,510,503,509,507,504,511,510,508,506,505,506,508,511,510,504,509,503,507,505,507,503,510,506,504,508,509,511,505,504,511,503,510,508,509,506,505,511,503,504,509,510,508,506,505,506,510,508,509,503,511,504,505,511,504,509,510,503,508,506,505,510,504,503,509,511,508,506,505,503,508,504,506,511,510,509,505,511,510,504,508,506,503,509,505,504,509,506,510,503,511,508,505,504,509,510,508,506,511,503,505,508,510,506,509,511,504,503,505,511,508,510,504,509,506,503,505,506,511,510,508,503,509,504,505,508,504,510,509,506,511,503,507,505,510,506,509,511,503,504,508,507,505,508,511,510,506,509,503,504,507,505,509,507,511,508,503,504,510,506,505,507,511,503,504,506,510,509,508,505,504,510,509,506,503,507,508,511,505,504,508,506,507,511,503,510,509,505,506,507,509,508,511,503,504,510,505,504,509,510,511,503,508,506,507,505,508,510,504,503,509,511,507,506,505,504,511,503,507,509,510,508,506,505,508,506,507,503,511,510,509,504,505,507,509,504,510,508,511,503,506,511,508,507,506,503,504,510,509,503,506,504,508,511,510,509,507,511,504,509,503,507,510,506,508,504,507,510,509,508,511,503,506,509,506,503,511,510,508,504,507,503,511,509,506,510,504,508,507,505,503,506,509,511,510,508,504,507,505,510,506,511,509,503,504,505,508,507,510,503,506,511,509,505,507,508,504,506,509,510,511,503,508,504,507,505,510,511,503,506,509,508,504,507,505,511,503,509,510,506,504,508,512,507,513,505,506,509,503,510,511,504,512,513,505,508,
Execution time using the thread pool:6447
实现一个取消选项
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program4
{
static void AsyncOperation1(CancellationToken token)
{
Console.WriteLine("Startting the fist task");
for(int i = 0; i < 5; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("The first task has been cancelled");
return;
}
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("The first task has completed successfully");
}
static void AsyncOperation2(CancellationToken token)
{
try
{
Console.WriteLine("Startting the second task");
for(int i = 0; i < 5; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("The second task has completed successfuly");
}
catch (OperationCanceledException)
{
Console.WriteLine("The second task has been canceled");
}
}
static void AsyncOperation3(CancellationToken token)
{
bool cancellationFlag = false;
token.Register(() => cancellationFlag = true);
Console.WriteLine("Starting the third task");
for(int i = 0; i < 5; i++)
{
if (cancellationFlag)
{
Console.WriteLine("The third task has been canceled");
return;
}
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine("The third task has completed successully");
}
static void Main()
{
using(var cts=new CancellationTokenSource())
{
var token = cts.Token;
ThreadPool.QueueUserWorkItem(_ => AsyncOperation1(token));
Thread.Sleep(TimeSpan.FromSeconds(2));
cts.Cancel();
}
using (var cts = new CancellationTokenSource())
{
var token = cts.Token;
ThreadPool.QueueUserWorkItem(_ => AsyncOperation2(token));
Thread.Sleep(TimeSpan.FromSeconds(2));
cts.Cancel();
}
using (var cts = new CancellationTokenSource())
{
var token = cts.Token;
ThreadPool.QueueUserWorkItem(_ => AsyncOperation3(token));
Thread.Sleep(TimeSpan.FromSeconds(2));
cts.Cancel();
}
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
}
output:
Startting the fist task
Startting the second task
The first task has been cancelled
Starting the third task
The second task has been canceled
The third task has been canceled
本程序使用了三种方式来实现取消过程,第一个是轮询检查CancellationToken
的IsCancellationRequested
属性,如果该属性为true
,则说明操作需要被取消,而CancelationToken
的IsCancellationRequested
属性是直接受CancellationTokenSource
的Cancel
方法控制的。
第二种是抛出一个OperationCancelledException
异常,者允许在操作之外控制取消过程。
最后一个方式就是注册一个回调 函数,当操作被取消时,在线程池将调用该回调函数。这允许链式传递一个取消逻辑到另一个异步操作中。
在线程池中使用等待事件处理器及超时
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program5
{
static void RunOperations(TimeSpan workerOperationTimeout)
{
using(var evt=new ManualResetEvent(false))
using(var cts=new CancellationTokenSource())
{
Console.WriteLine("Registering timeout operation....");
var worker = ThreadPool.RegisterWaitForSingleObject(evt,(state,isTimedOut)=>
{
WorkerOperationWait(cts, isTimedOut);
},
null,workerOperationTimeout,true);
Console.WriteLine("Starting long running operation....");
ThreadPool.QueueUserWorkItem(_=>WorkerOperation(cts.Token,evt));
Thread.Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2)));
worker.Unregister(evt);
}
}
static void WorkerOperation(CancellationToken token,ManualResetEvent evt)
{
for(int i = 0; i < 6; i++)
{
if (token.IsCancellationRequested)
return;
Thread.Sleep(TimeSpan.FromSeconds(1));
}
evt.Set();
}
static void WorkerOperationWait(CancellationTokenSource cts,bool isTimeout)
{
if (isTimeout)
{
cts.Cancel();
Console.WriteLine("Worker operation timed out and was cancelled");
}
else
{
Console.WriteLine("Worker operation succeed");
}
}
static void Main()
{
RunOperations(TimeSpan.FromSeconds(5));
RunOperations(TimeSpan.FromSeconds(7));
}
}
}
output:
Registering timeout operation....
Starting long running operation....
Worker operation timed out and was cancelled
Registering timeout operation....
Starting long running operation....
Worker operation succeed
线程池还有个有用的方法,ThreadPool.RegisterWaitForSingleObject
。
var worker = ThreadPool.RegisterWaitForSingleObject(evt,(state,isTimedOut)=>
{
WorkerOperationWait(cts, isTimedOut);
},
null,workerOperationTimeout,true);
代码中的worker是RegisteredWaitHandle
类型,ThreadPool.RegisterWaitForSingleObject
方法中,第一个参数是evt
,即WaitHandle
类型,第二个参数是回调函数(该回调函数有两个参数,第一个是object state,第二个是bool isTimedOut),第三个参数是object state,本例中无,则为null,第四个是超时判定时间,第五个参数bool executeOnlyOnce。
所以这个方法实际上做了这样的一件事:对evt注册一个回调函数,当evt收到信号(evt.Set()被调用)或者超时(在判定时间内没有调用evt.Set()),将会调用回调函数,如果收到信号,那么传递给回调函数的isTimeOut
就是false,否则就是true。
在本例中,如果超时了,那么isTimedOut
由于是true,则取消WorkerOperation
操作,而WorkerOperation
至少需要6s,因为循环了6次,每次停1s,所以,第一个给的超出时间5s,是不够的,必定超时,只能取消操作,而第二个给的超出时间是7s,是可行的,因此操作会顺利完成。
当有大量的线程必须处于阻塞状态中等待一些多线程事件发信号时,以上方式非常有用。借助于线程池的基础设施,我们无需阻塞所有的这样的线程。可以释放这些线程直到信号事件被设置。在服务器端应用程序中,这是个非常重要的应用场景,因为服务器端应用程序要求高伸缩性及高性能。
使用计时器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program6
{
static Timer _timer;
static void TimerOperation(DateTime start)
{
var elapsed = DateTime.Now - start;
Console.WriteLine($"{elapsed.Seconds} seconds from {start}." +
$"Timer thread pool thread id:{Thread.CurrentThread.ManagedThreadId}");
}
static void Main()
{
Console.WriteLine("Press 'Enter' to stop the timer...");
var start = DateTime.Now;
_timer = new Timer(_=>TimerOperation(start),null,TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2));
try
{
Thread.Sleep(TimeSpan.FromSeconds(6));
_timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4));
Console.ReadLine();
}
finally
{
_timer.Dispose();
}
}
}
}
output:
Press 'Enter' to stop the timer...
1 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
3 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
5 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
7 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
11 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
15 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
19 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
23 seconds from 2022/2/13 13:49:04.Timer thread pool thread id:4
Timer实例的构造函数,第一个是一个lambda表达式,将会在线程池中被执行,第二个参数是object state,本例为null,第三个参数指定了什么时候会第一次运行lambda表达式,第四个参数指定了再次调用该lambda表达式的间隔时间。
之后主线程等待6s,而后修改计时器,采用Change
方法,第一个参数即多久后启动lambda表达式,第二个参数修正为每隔多久再次调用lambda表达式。
下面简单加上state:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program6
{
static Timer _timer;
static void TimerOperation(DateTime start,object state)
{
var elapsed = DateTime.Now - start;
Console.WriteLine($"{elapsed.Seconds} seconds from {start}." +
$"Timer thread pool thread id:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine(state.ToString());
}
static void Main()
{
Console.WriteLine("Press 'Enter' to stop the timer...");
var start = DateTime.Now;
_timer = new Timer((state)=>TimerOperation(start,"TestState"),null,TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2));
try
{
Thread.Sleep(TimeSpan.FromSeconds(6));
_timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4));
Console.ReadLine();
}
finally
{
_timer.Dispose();
}
}
}
}
output:
Press 'Enter' to stop the timer...
1 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:4
TestState
3 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:4
TestState
5 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:5
TestState
7 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:4
TestState
11 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:5
TestState
15 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:4
TestState
19 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:5
TestState
23 seconds from 2022/2/13 13:56:47.Timer thread pool thread id:4
可以以更复杂的方式使用计时器,比如,可以通过Timeout.Infinite
值提供给计时器,一个间隔参数来只允许计时器操作一次。然后在计时器异步操作内,能够设置下一次计时器操作将被执行的时间。
使用BackgroundWorker
组件
使用BackgroundWorker
,可以将异步代码组织为一系列事件及事件处理器。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program7
{
static void Main()
{
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += Worker_DoWork;
bw.ProgressChanged += Worker_ProgressChanged;
bw.RunWorkerCompleted += Worker_Completed;
bw.RunWorkerAsync();
Console.WriteLine("Press C to cancel work");
do
{
if (Console.ReadKey(true).KeyChar == 'C')
{
bw.CancelAsync();
}
}
while (bw.IsBusy);
}
private static void Worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine($"Completed thread pool thread id:{Thread.CurrentThread.ManagedThreadId}");
if (e.Error != null)
{
Console.WriteLine($"Exception {e.Error.Message} has occured");
}
else if (e.Cancelled)
{
Console.WriteLine($"Operation has been canceled");
}
else
{
Console.WriteLine($"The answer is {e.Result}");
}
}
private static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine($"{e.ProgressPercentage} % completed" +
$"Progresss thread pool thread id:{Thread.CurrentThread.ManagedThreadId}");
}
private static void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine($"DoWork thread pool threadd id:{Thread.CurrentThread.ManagedThreadId}");
var bw = (BackgroundWorker)sender;
for(int i = 1; i <= 100; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
return;
}
if (i % 10 == 0)
{
bw.ReportProgress(i);
}
Thread.Sleep(TimeSpan.FromSeconds(0.1));
}
e.Result = 42;
}
}
}
output:
按下C
:
Press C to cancel work
DoWork thread pool threadd id:3
10 % completedProgresss thread pool thread id:4
20 % completedProgresss thread pool thread id:5
30 % completedProgresss thread pool thread id:4
40 % completedProgresss thread pool thread id:5
50 % completedProgresss thread pool thread id:4
Completed thread pool thread id:5
Operation has been canceled
不按C
:
Press C to cancel work
DoWork thread pool threadd id:3
10 % completedProgresss thread pool thread id:4
20 % completedProgresss thread pool thread id:5
30 % completedProgresss thread pool thread id:4
40 % completedProgresss thread pool thread id:5
50 % completedProgresss thread pool thread id:4
60 % completedProgresss thread pool thread id:5
70 % completedProgresss thread pool thread id:4
80 % completedProgresss thread pool thread id:5
90 % completedProgresss thread pool thread id:4
100 % completedProgresss thread pool thread id:5
Completed thread pool thread id:4
The answer is 42
bw的允许发送报告,允许取消选项均被设置为true,对三个事件也进行了注册,在dowork中一般都要做两个事,一是轮询检查是否取消,二是不断报告进度,当进度有变时,又触发进度变化事件。
这种基于事件的异步模式称为EAP(Event-based Asynchronous Pattern),是历史上第二种用来构造异步程序的方式,现在更推荐TPL,即基于任务的异步程序模式。
BackgroundWorker
实际上广泛用于WPF中,通过后台工作事件处理器的代码可以直接与UI控制器交互。