c#线程和同步
18第 章
线程和同步
在应用程序中应行应应用需要一定的应应。用应不希望在安用应界面应只是等待~直到网装
服应器返回一应应止。用应可以在应应程中应行其他一些操作~甚至取消应送应服应器的应求个响个。应些都可以使用应程应应。来
使用应程有原因。不应用应等待是其中一原因。应于所有需要等待的操作~例如几个个
文件、据应或应应应的应都需要一定的应应~此应就可以应一新应程~完成其他任应。数网启启个即
使是应理密集型的任应~应程也是有助的。一应程的多应程可以同应行在不同的帮个个运上~CPU或多核心个的不同核心上。CPU
应必应注意行多应程的一些应应。应可以同应行~但如果应程应应相同的据~就运个它运数很
容易出应应。必应应应同步
。
本章介应用多应程应应用程序所需了解的知应~包括,个写
应程述概?
使用委托的应型应程?
应程应?
应程池?
应程应应?
同步技应?
空应?COM
?BackgroundWorker
概述18.1
应程是程序中立的指令流。使用独应任何程序应~都有一入口,写个方法。C#Main()程序从方法的第一应句应始应行~直到应方法返回应止。条个Main()
应程序应非常适合于有一可应应的任应序列的程序~但程序常常需要同应完成多个构个个
任应。应程应客应端和服应器端应用程序都非常重要。在应应器中应入代应应~Visual StudioC#
第?部分 基 应 应
窗会即与会帮口立应示所应入代应相应的主应。后台应程搜索助。Dynamic HelpMicrosoft Word
的应应器也做相同的事。一应程等待用应应入~一应程应行后台搜索。第三应程拼写会个另个个将
写数个从入的据存应在应应文件中~第四应程上下应其他据。数Internet
运个称听行在服应器上的应用程序中~一应程等待客应的应求~应应应器应程。只要接收到应
求~就把应送应一工作应程~之后应应客应通信。应器应程立返回~接收下一客它另个与听会即个
应应送的下一应求。个
使用任应管理器~可以菜应从中打应列~应WindowsView | Select ColumnsThreads
看应程和每应程的应程。在应个号中~只有运个行在一应程中~其他应用程序都使18-1cmd.exe用多应程。个运行了个应程。Internet Explorer51
应 18-1
操作系应应度应程。应程有一应先应、正在应理的程序的位置应器、一存应其本地应会个数个
量的堆应。每应程都有自己的堆应~但程序代应的存和堆由一应程的所有应程共享。应使个内个
一应程中的所有应程之应的通信非常快――应应程的所有应程都应址相同的应应存。但是~应个虚内
也使应理比应困应~因应多应程可以修改同一存位置。个个内
应程管理的应源包括应应存和虚内句柄~其中至少包含一应程。应程是行程个运Windows序所必需的。
在中~托管的应程由应定应。托管的应程不一定映射应一操作系应应程。个.NETThread
尽况管应应情可能出应~但应由运将行应应应托管的应程映射到操作系应的物理应程上。在应.NET
方面~的行主机运与应用程序的行主机完全不同。使用运SQL Server 2005Windows
应可以应得部应程的信息~但在托管的应用程序中~通常最好使用托管的应程内。ProcessThread
246
第18章 应程和同步异步委托18.2
应建应程的一应应应方式是定应一委托~步应用。第个异它章提到~委托是方法的应型7
安全的引用。应应支持步应用方法。在后台~异应应建一应行任应的应程会个。DelegateDelegate
提示,
委托使用应程池完成步任应。应程池应应本章后面的容。来异内
应了演示委托的步特性~应一方法~需要一定的应应才能应行完应。方法异启个它
至少需要作应应元应送应的毫秒才能应行完~因应应用了来数它方法,TakesAWhileThread.Sleep()static int TakesAWhile(int data, int ms){
Console.WriteLine("TakesAWhile started");Thread.Sleep(ms);
Console.WriteLine("TakesAWhile completed");return ++data;
}
要在委托中应用应方法~必应定应一有相同和返回应型的委托~如下面的个个参数
所示,TakesAWhileDelegate
public delegate int TakesAWhileDelegate(int data, int ms);
应在可以使用不同的技应步应用委托~返回应果。异
投票18.2.1
一应技应是投票~应应委托是否完成了任应。所应建的应提供了方法Delegate
~在应方法中~可以应送用委托应型定应的应入。参数方法应是有BeginInvoke()BeginInvoke()
两个和应型的应外参数稍后应应。应在重要的是方法AsyncCallbackObject()BeginInvoke()
的返回应型,。在中~可以应得委托的信息~应应委托是否完并IAsyncResultIAsyncResult
成了任应~应是属没性的功应。只要委托有完成其任应~程序的主应程就应应应行IsCompleted
循应。while
static void Main()
{
// synchronous
// TakesAWhile(1, 3000);
// asynchronous
TakesAWhileDelegate d1 = TakesAWhile;
247
第?部分 基 应 应
IAsyncResult ar = d1.BeginInvoke(1, 3000, null, null);
while (!ar.IsCompleted)
{
// doing something else in the main thread
Console.Write(".");
Thread.Sleep(50);
}
int result = d1.EndInvoke(ar);
Console.WriteLine("result: {0}", result);
}
运运行应用程序~可以看到主应程和委托应程同应行~在委托应程应行完应后~主应程就停止循应。
.TakesAWhile started
...................................................TakesAWhile
completed
result: 2
除了应应委托是否完成之外~应可以在完成了由主应程应行的工作后~应用委托应型的
方法。方法一直等待~直到委托完成其任应应止。会EndInvoke()EndInvoke()
警告,
如果不等待委托完成其任应就应束主应程~委托应程就停止。会
等待句柄18.2.2
等待步委托的应果的一应方式是使用异另与相应的等待句柄。使用IAsyncResult
属个属个性可以应应等待句柄。应性返回一应型的应象~可以它AsyncWaitHandleWaitHandle等待委托应程完成其任应。方法将个个参数一超应应应作应可应的第一~在其中可以WaitOne()
定应要等待的最大应应。应里应置应毫秒。如果应生超应~就返回~循应50WaitOne()falsewhile会个断应应应行。如果等待操作成功~就用一中退出循应~用委托的方whileEndInvoke()法接收应果。
static void Main()
{
TakesAWhileDelegate d1 = TakesAWhile;
IAsyncResult ar = d1.BeginInvoke(1, 3000, null, null);
while (true)
{
248
第18章 应程和同步
Console.Write(".");
if (ar.AsyncWaitHandle.WaitOne(50, false))
{
Console.WriteLine("Can get the result now");
break;
}
}
int result = d1.EndInvoke(ar);
Console.WriteLine("result: {0}", result);
}
提示,
等待句柄的容应应本章后面的“同步”一应。内
异步回应18.2.3
等待委托的应果的第三应方式是使用步回应。在异方法的第三中个参数~BeginInvoke()
可以应送一应足个委托的需求的方法。委托定应了一个AsyncCallbackAsyncCallback
应型的~其返回应型是参数。应里~把方法的地址IAsyncResultvoidTakesAWhileCompleted
应予第三~以应足个参数委托的需求。应于最后一~可以应送任意应象个参数~AsyncCallback
以便回应方法中应应。应送委托应例是可行的~应应回应方法就可以使用应得步方法的应果从它它异。
应在~只要委托完成了其任应~就应用TakesAWhileDelegateTakesAWhileCompleted()
方法。不需要在主应程中等待应果。但是在委托应程的任应未完成之前~不能停止主应程~除
非应应停止的委托应程有出应应。没
static void Main()
{
TakesAWhileDelegate d1 = TakesAWhile;
d1.BeginInvoke(1, 3000, TakesAWhileCompleted, d1);
for (int i = 0; i < 100; i++)
{
Console.Write(".");
Thread.Sleep(50);
}
}
方法用委托指定的和返回应型定应。参数来TakesAWhileCompleted()AsyncCallback
用方法应送的最后一可以使用个参数应取。在BeginInvoke()ar. AsyncState
249
第?部分 基 应 应
委托中~可以应用方法应得应果。TakesAWhileDelegateEndInvoke()
static void TakesAWhileCompleted(IAsyncResult ar)
{
if (ar == null) throw new ArgumentNullException("ar");
TakesAWhileDelegate d1 = ar.AsyncState as TakesAWhileDelegate;
Trace.Assert(d1 != null, "Invalid object type");
int result = d1.EndInvoke(ar);
Console.WriteLine("result: {0}", result);
}
警告,
使用回应方法~必应注意应方法在委托应程中应用~而不是在主应程中应用。个
除了定应一应的方法~应应应送个独它方法之外~匿名方法也非常适合应应情BeginInvoke()
况个参数。使用委托应应字~可以定应一匿名方法~其是应型。应在不需要把一IAsyncResult个应应予方法的最后一~因应匿名方法可以直接应应应方法外部的应量个参数。BeginInvoke()d1但是~匿名方法仍是在委托应程中应用~以应应方式定应方法应~应不是明应。很
static void Main()
{
TakesAWhileDelegate d1 = TakesAWhile;
d1.BeginInvoke(1, 3000,
delegate(IAsyncResult ar)
{
int result = d1.EndInvoke(ar);
Console.WriteLine("result: {0}", result);
},
null);
for (int i = 0; i < 100; i++)
{
Console.Write(".");
Thread.Sleep(50);
}
}
提示,
只有代应不太多~且应应代应不需要用于不同的地方应~才应使用匿名方法。在应应情下况定应一应应的方法比应好。匿名方法应应第个独章。7
250
第18章 应程和同步应程模型和所有应些利用步委托的应应异—— 投票、等待句柄和步应用异—— 不应能用于委
托~应程模型在的各地方都能应到。例如~可以用个应的.NET FrameworkHttpWebRequest
方法步应送异应求~使用应的BeginGetResponse()HTTP WebSqlCommandBeginExecute- 方法应据应应送步应求。其应似于委托的数异参数方法~也可以使用相Reader()BeginInvoke()
同的方式应得应果。
提示,
参应第章~参应第章。HttpWebRequest35SqlCommand25
应18.3 Thread
使用应可以应建和控制应程。下面的代应是应建和应一新应程的应应例子启个。Thread
应的造函接受构数和应型的委托参数。ThreadThreadStartParameterizedThreadStart
委托定应了一返回应型应个的无方法。在应建了参数应象后~就可以ThreadStartvoidThread
用方法应应程了,启Start()
using System;
using System.Threading;
namespace Wrox.ProCSharp.Threading
{
class Program
{
static void Main()
{
Thread t1 = new Thread(ThreadMain);
t1.Start();
Console.WriteLine("This is the main thread.");
}
static void ThreadMain()
{
Console.WriteLine("Running in a thread.");
}
}
}
运个两个行应程序~得到应程的应出,
This is the main thread.
Running in a thread.
251
第?部分 基 应 应
不能保应应果先应出。应程由操作系应应度~每次应程在前面都是不同的。哪个哪个
前面探应了匿名方法如何步委托一起使用。步委托应可以与异异与应一起使用~Thread
将应程方法的应应代应应送应构数造函的应元,Thread
using System;
using System.Threading;
namespace Wrox.ProCSharp.Threading{
class Program
{
static void Main()
{
Thread t1 = new Thread(
delegate()
{
Console.WriteLine("running in a thread");
});
t1.Start();
Console.WriteLine("This is the main thread.");
}
}
}
在应建好应程后~如果不需要用引用应程的应量控制应程~应可以用更应应的方式应代应来写。
用造函应建一新的构数个应象~匿名方法应送应造函~用返回的将构数应象直ThreadThread
接应用方法,Start()
using System.Threading;
namespace Wrox.ProCSharp.Threading{
class Program
{
static void Main()
{
new Thread(
delegate()
{
Console.WriteLine("running in a thread");
}).Start();
Console.WriteLine("This is the main thread.");
252
第18章 应程和同步
}
}
}
但是~采用一引用个应象的应量是有原因的。例如~应了更好地控制应程~可以Thread
在应应程前~应置启属称当称静属性~应应程指定名。应了应得前应程的名~可以使用应应性Name
~应取前应程的当应例~应应属性~应行应取应应。应程也有一Thread.CurrentThreadThreadName
个托管的应程~可以用属它性应取。IDManagedThreadId
static void Main()
{
Thread t1 = new Thread(ThreadMain);t1.Name = "MyNewThread1";
t1.Start();
Console.WriteLine("This is the main thread.");}
static void ThreadMain()
{
Console.WriteLine("Running in the thread {0}, id: {1}.",
Thread.CurrentThread.Name,
Thread.CurrentThread.ManagedThreadId);}
在应用程序的应出中~应在应可以看到应程名和,ID
This is the main thread.
Running in the thread MyNewThread1, id: 3.警告,
应应程指定名~非常有助于应应应程。在称的应应应中~可以打应会Visual StudioDebug 工具应~应看应程的名。称Location
应应程应送据数18.3.1
如果需要应应程应送一些据~可以采用应方式。一应方式是使用应数两
委托的参数构数另个造函~一应方式是应建一定制应~把应程ParameterizedThreadStartThread
的方法定应应应例方法~应应就可以初始化应例的据~之后应应程。数启
要应应程应送据~需要某存应据的应或应。应里定应了包含字数个数构构符串的应~也Data
可以应送任意应象。
public struct Data
253
第?部分 基 应 应
{
public string Message;
}
如果使用了委托~应程的入口点必应有一个应型的参ParameterizedThreadStartobject
数~返回应型应。应象可以应应应据~应里是把信息入控制台。数写void
static void ThreadMainWithParameters(object o){
Data d = (Data)o;
Console.WriteLine("Running in a thread, received {0}", d.Message);
}
在应的造函中~可以新的入口构数将点应予~应送应ThreadThreadMainWithParameters
量~应用方法。dStart()
static void Main()
{
Data d = new Data();
d.Message = "Info";
Thread t2 = new Thread(ThreadMainWithParameters);t2.Start(d);
}
应新应程应送据的一应方式是定应一应数另个参;应应,~在其中定应需要的字MyThread
段~应程的主方法定应应应的一应例方法,将个
public class MyThread
{
private string data;
public MyThread(string data)
{
this.data = data;
}
public void ThreadMain()
{
Console.WriteLine("Running in a thread, data: {0}", data);
}
}
应应~就可以应建的一应象~应个应的造函应送应象和构数MyThreadThreadThread
254
第18章 应程和同步方法。应程可以应应据。数Main()
MyThread obj = new MyThread("info");
Thread t3 = new Thread(obj.ThreadMain);t3.Start();
后台应程18.3.2
只要有一前台应程在行~应用程序的应程就在行。如果多前台应程在行~而个运运个运
方法应束了~应用程序的应程就是激活的~直到所有前台应程完成其任应应止。Main
在默应情下~用况应应建的应程是前台应程。应程池中的应程应是后台应程。Thread
在用应应建应程应~可以应置性属~以定应应程是前台应程应是后台确ThreadIsBackground
应程。方法应程将的属性应置应默应应。在应新应程后~主应启Main()t1IsBackgroundfalse()
程就把应束信息入控制台。新应程入应和应束信息~在应应程中要写会写启个它睡眠秒。在应3秒中~新应程完成其工作~主应程才应束。会3
class Program
{
static void Main()
{
Thread t1 = new Thread(ThreadMain);
t1.Name = "MyNewThread1";
t1.IsBackground = false;
t1.Start();
Console.WriteLine("Main thread ending now...");
}
static void ThreadMain()
{
Console.WriteLine("Thread {0} started",
Thread.CurrentThread.Name);
Thread.Sleep(3000);
Console.WriteLine("Thread {0} completed",
Thread.CurrentThread.Name);
}
}
在应应用程序应~看到入控制台的完成信息~管主应程启会写尽会早一步完成其工作。
原因是新应程也是一前台应程。个
Main thread ending now...
255
第?部分 基 应 应
Thread MyNewThread1 started
Thread MyNewThread1 completed
如果应新应程的将启属性改应~应示在控制台上的应果就不同。在会IsBackgroundtrue
一系应上~可以看到新应程的应信息~但有应束信息。如果应程有正常应束~应有可能个启没没
看不到应信息。启
Main thread ending now...
Thread MyNewThread1 started
后台应程非常适合于完成后台任应。例如~如果应应应用程序~应应器应应行拼写运Word
其应程就有意应了。在应用程序应束应~应应器应程就可以应应了。但是~应应没拼写信息应Outlook的应程应一直是激活的~直到应束~才应束。它Outlook
应程的应先应18.3.3
前面提到~操作系应是在应度应程。应应程指定应先应~就可以影响个应应应度。
在改应应先应之前~必应理解应程应度器。操作系应根据应先应应度应程。应先应最来高的应程在
上行。应程如果在等待应源~就停止行~应运会运放。应程必应等待有原因~例几个CPUCPU
如应响睡眠指令、等待磁应的完成~等待应包的到等。如果应程不是主应应网达放~应I/OCPU程应度器就应先安会个排应应程。如果应程有一应应量~就可以应应使用。如果应先应相同的多CPU个应程等待使用~应程应度器就使用一循应应度应应~会个将逐交个应应程使用。如果应CPUCPU
程是被其他应程应先了~就它会排在应列的最后。
只有应先应相同的多应程在行~才用得上应应量和循应应应。应先应是应应的。如果应程是个运
密集型的;一直需要~且不等待应源,~其应先应就低于用应应程定应的基本应先应。如CPUCPU
果应程在等待应源~就会它会个推应应先应向上移应~的应先应就增加。由于有应推应~应程才有可能在下次等待应束应应得。CPU
在应中~可以应置属响性~以影应程的基本应先应。属性需要ThreadPriorityPriority一个枚应定应的应。应应定应的应应有、、和ThreadPriorityHighestAboveNormalBelowNormal
。在应应程指定应高的应先应应要小心~因应应可能降低其他应程的行运几率。如果需要~Lowest
可以改应应先应一段应短的应应。
控制应程18.3.4
应用应象的方法~可以应建应程。但是~在应用方法后~ThreadStart()Start()新应程仍不在状应~而是在状运应。操作系应的应程应度器应应了要行的RunningUnstarted
应程后~应程就改应会状应。应取属当性~就可以应得应程的RunningThread.ThreadState前应。状
使用方法~使应程应于会状应~在用方法定应的应应应Thread.Sleep()WaitSleepJoinSleep()后~应程就会再次被应用。
要停止一应程~可以应用另个方法。应用应方法应~在接到中止个会命Thread.Abort()
256
第18章 应程和同步令的应程中抛出应型的常。用一应理程序异个个异捕应应常~应程可以ThreadAbortException
在应束前完成一些理工作。应程应可以在接收到应用清方法的应果Thread.ResetAbort()
后应应行。如果应程有重置中止~接收到中止应求的应程应应应运没状将从ThreadAbortException
改应。AbortRequestedAborted
如果需要等待应程的应束~就可以应用方法。方法停止会Thread.Join()Thread.Join()当它前应程~把应置应状应~直到加入的应程完成应止。WaitSleepJoin
也支持和方法~应分应用于应停和应应一它.NET 1.0Thread.Suspend()Thread.Resume()个应程。但是~应程在得到应求应~我应不知并它道应程在做什应~可能应于有应的同步Suspend
段中。应容易应很弃另致死应。应就是应些方法应在被应的原因。外~应可以使用同步应象应应程应信号挂状~应应应程就可以起了。于是~应程就知道应入等待应的最佳应机。
应程池18.4
应建应程是需要应应的。如果有不同的小任应要完成~就可以事先应建应多应程~在应完成应些任应应应出应求。应应程应在需要更多的应程应个数减增加~在需要应放应源应少。
不需要自己应建应应一列个表。应列表由应管理。应应在需要应个会减增应池中应ThreadPool
程的~直到最大的应程。池中的最大应程是可以个数数数双配置的。在核中~默应应置应CPU个工作应程和个应程。也可以指定在应建应程池应应立应的最即启数小应程~以及应程501000I/O
池中可用的最大应程。如果有更多的工作要应理~应程池中应程的使用也到了数极限~最新的工作就要排应~必应等待应程完成其任应。
下面的示例程序首先要应取工作应程和应程的最大应程~把应信息入控制台数个写。I/O
接着在循应中~应用方法~应送一个应forThreadPool.QueueUserWorkItem()WaitCallback型的委托~把方法应予应程池中的应程。应程池收到应应求后~就池中应应个会从JobForAThread()
一应程~应用应方法。如果应程池应有行~就应建一应程池~应第一应程。如果应个来没运会个启个
程池已应在行~且有一自由应程~就把工作应送应应应程。运个个
using System;
using System.Threading;
namespace Wrox.ProCSharp.Threading
{
class Program
{
static void Main()
{
int nWorkerThreads;
int nCompletionPortThreads;
ThreadPool.GetMaxThreads(out nWorkerThreads, out
nCompletion
257
第?部分 基 应 应
PortThreads);
Console.WriteLine("Max worker threads: {0}, I/O
completion
threads: {1}",
nWorkerThreads, nCompletionPortThreads);
for (int i = 0; i < 5; i++)
{
ThreadPool.QueueUserWorkItem(JobForAThread);
}
Thread.Sleep(3000);
}
static void JobForAThread(object state)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine("loop {0}, running inside pooled thread
{1}", i,
Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(50);
}
}
}
}
运行应用程序~可以看到个当工作应程的前应置。个两个任应只由应程池中的应程应505
理~应者运与数行应程序的应果可能此不同~也可以改应任应的睡眠应应和要应理的任应~得到完全不同的应果。
Max worker threads: 50, I/O completion threads: 1000
loop 0, running inside pooled thread 4
loop 0, running inside pooled thread 3
loop 1, running inside pooled thread 4
loop 1, running inside pooled thread 3
loop 2, running inside pooled thread 4
loop 2, running inside pooled thread 3
loop 0, running inside pooled thread 4
loop 0, running inside pooled thread 3
loop 1, running inside pooled thread 4
loop 1, running inside pooled thread 3
258
第18章 应程和同步
loop 2, running inside pooled thread 4
loop 2, running inside pooled thread 3
loop 0, running inside pooled thread 4
loop 1, running inside pooled thread 4
loop 2, running inside pooled thread 4
应程池使用起应应~但有一些来很它限制,
应程池中的所有应程都是后台应程。如果应程中的所有前台应程都应束了~所有的后台?
应程就停止。不能把应程池中的应程改应前台应程。会
不能应应程池中的应程应置应先应或名。称?
应于应象~应程池中的所有应程都是多应程应元?COM(multithreaded
~应程。应多应象都需要应应程应元apartmentMTA)COM(single-threaded
~应程。apartmentMTA)
应程池中的应程只能用于应应应短的任应。如果应程要一直行运;如的应应器拼写?Word
应程,~就应使用应应建一应程。个Thread
应程应应18.5
用多应程应程不容易。在应应应相同据的多应程应~个并启数个会遇到应以应应的应应。应了避免应些应应~必应特应注意同步应应和多应程可能应生的其他应应。下面探应应程相应的应应~如应应个与条
件和死应。
应应件条18.5.1
如果或多应程应应相同的应象~或两个个状会条者应应不同步的共享应~就出应应应件。
应了演示应应件~定应一条个应~包含一它个字段和一方法个StateObjectintChange
。在方法的应应代应中~应应应量是否包含。如果是~就应增其应。下StateChangeStatestate5
一应句是个~应应它应在是否包含。在应包含的应量应增了后~应应量Trace.Assertstate651的应就应是。但事应不一定是应应。例如~如果一应程应应应行完个应句~就它被其6if(state ==5)他应程应先~应度器去运另个个行一应程了。第二应程应在应入体~由于的应仍是~所ifstate5以应将它增应。第一应程应在个个再次被安排应行~在下一应句中~被应增应。应应就应生6state7了应应件~应示条断言信息。
public class StateObject
{
private int state = 5;
public void ChangeState(int loop)
{
if (state == 5)
{
259
第?部分 基 应 应
state++;
Trace.Assert(state == 6, "Race condition occurred after " +
loop + " loops");
}
state = 5;
}
}
下面定应一应程方法应应应一个来点。应的方法将个一SampleThreadRaceCondition()
应象作应其。在一无参数个限循应中~应用方法。应量应用StateObjectwhileChangeState()i
于应示断数言信息中的循应。
public class SampleThread
{
public void RaceCondition(object o){
Trace.Assert(o is StateObject, "o must be of type StateObject");
StateObject state = o as StateObject;
int i = 0;
while (true)
{
state.ChangeState(i++);
}
}
}
在程序的方法中~应建了一新的个应象~由所有的应程共享。在它MainStateObject应的造函中~应构数的地址应送一个应型的应象~以应建ThreadRaceConditionSampleThread应象。接着应送应象~使用方法应应应应程。启个ThreadstateStart()
static void Main()
{
StateObject state = new StateObject();for (int i = 0; i < 20; i++)
{
new Thread(new SampleThread().RaceCondition).Start(state);
}
}
启会条条条应程序~就出应应应件。在应应件第一次出应后~应需要多应应应才能第二次出应应应
件~取于系应以决将数会及程序建立应应布版本应是应应版本。如果建立应应布版本~应应应的出应次
260
第18章 应程和同步比应多~因应代应被应化了。如果系应中有多个或使用核双~其中多应程可以同应个运CPUCPU
行~应应应也比应核会的出应次多。在应核数中~若应程应度是应先式的~也出应应应应~会CPUCPU
只是有没那应应繁。
应应示在个条断启几会循应后~应生应应件的程序言。多应应用程序次~应是得18-23816
到不同的应果。
应 18-2
要避免应应应~可以应定共享的应象。应可以在应程中完成,用下面的应句应定在应程lock中共享的应量。只有一应程能在应定应中应理共享的个应象。由于应应象由所有的应程个statestate
共享~因此如果一应程应定了个~一应程就必应等待应应定的解除。一另个旦应行了应定~应state
程就应有应应定~直到应应定应的末尾才解除应定。如果每改应个应量引用的应象的应程都使用state一应定~应应件就不出应。个条会
public class SampleThread
{
public void RaceCondition(object o)
{
Trace.Assert(o is StateObject, "o must be of type StateObject");
StateObject state = o as StateObject;
int i = 0;
while (true)
{
lock (state) // no race condition with this lock
{
state.ChangeState(i++);
}
}
}
}
261
第?部分 基 应 应
在使用共享应象应~除了应行应定之外~应可以共享应象应置应应程安全的应象。其中将
方法包含一个应句。由于不能应定应量本身只有引用应型才能用于应ChangeState()lockstate(
定~因此定应一个应型的应量~用于将它应句。如果每次应都使用同)objectsynclockstate一同步应象修改应定~应应件就不出应。个来条会
public class StateObject
{
private int state = 5;
private object sync = new object();
public void ChangeState(int loop)
{
lock (sync)
{
if (state == 5)
{
state++;
Trace.Assert(state == 6, "Race condition occurred after "
+
loop + " loops");
}
state = 5;
}
}
}
死应18.5.2
应多的应定也有会两个挂两麻应。在死应中~至少有应程被起~等待应方解除应定。由于个将应程都在等待应方~就出应了死应~应程无限等待下去。
应了演示死应~下面应例化两个应型的应象~应送应并应的造构StateObjectSampleThread函。应建应程~其中一应程行方法数两个个运~一应程行方法另个运Deadlock1()Deadlock2():
StateObject state1 = new StateObject();
StateObject state2 = new StateObject();
new Thread(new SampleThread(state1, state2).Deadlock1).Start();
new Thread(new SampleThread(state1, state2).Deadlock2).Start();
方法和应在改应应象两个和的应。应就应行了应定。方状两个Deadlock1()Deadlock2()s1s2
法先应定~接着应定。方法先应定~再应定。应在~有可Deadlock1()s1s2Deadlock2()s2s1
262
第18章 应程和同步
能方法中的应定会被解除。接着出应一次应程切应~应始行~应定运并Deadlock1()s1Deadlock2()
。第二个应程应在等待应定的解除。因应需要等待~所以应程应度器它个再次应度第一应程~s2s1
但第一应程在等待个应定的解除。应应程应在都在等待~只要应定应有应束~就不解两个没会s2
除应定。应是一个典型的死应。
public class SampleThread{
public SampleThread(StateObject s1, StateObject s2)
{
this.s1 = s1;
this.s2 = s2;
}
private StateObject s1;private StateObject s2;public void Deadlock1(){
int i = 0;
while (true)
{
lock (s1)
{
lock (s2)
{
s1.ChangeState(i);
s2.ChangeState(i++);
Console.WriteLine("still running, {0}", i);
}
}
}
}
public void Deadlock2(){
int i = 0;
while (true)
{
lock (s2)
{
lock (s1)
263
第?部分 基 应 应
{
s1.ChangeState(i);
s2.ChangeState(i++);
Console.WriteLine("still running, {0}", i);
}
}
}
}
}
应果是~程序行了应多循应~不运没响运久就有应了。“仍在行”的信息应在控制台上写几决运入次。死应应应的应生应率也取于系应配置~每次行的应果都不同。
死应应应不应是明应。一应程应定了并很个~接着应定~一应程应定了另个~接着应s1s2s2定。只需改应应定应序~应应程就以相同的应序应行应定。但是~应定可能应两个会藏在方法的深s1
应。应了避免应应应~可以在应用程序的系个体构从架中~一应始就应应好应定应序~也可以应应定定应超应应应。如何定应超应应应应应下一应的容。内
同步18.6
要避免同步应应~最好不要在应程之应共享据。数当并然~应不应是可行的。如果需要共享据~就必应使用同步技应~保一次只有一应程应应和改应共享应。注意~同步应应应应数确个状与
条很找件和死应有应。如果不注意应些应应~就应在应用程序中到应应的原因~因应应程应应是不定期应生的。
本应应应可以用于多应程的同步技应,个
应句?lock
应?Interlocked
应?Monitor
等待句柄?
应?Mutex
应?Semaphore
应?Event
应句、应和应可用于应程部的同步。内应、lockInterlockedMonitorMutex
应和应提供了多应程中的应程同步。个SemaphoreEvent
应句和应程安全18.6.1 lock
应多应程的同步提供了自己的应应字,个应句。应句是应置应定和解除应定的C#locklock一应应应方式。
在添加应句之前~先应入一应应应件。应另个条演示了如何使用应程共享lockSharedState
264
第18章 应程和同步
的应~保存一状并个数整应。
public class SharedState
{
private int state = 0;
public int State
{
get { return state; }
set { state = value; }
}
}
应包含方法~应方法是新应程的入口点。在其应应代应中~将TaskDoTheTask()
的应增次。应量在应应的造函中初始化,个构数SharedStateState50000sharedStatepublic class Task
{
SharedState sharedState;
public Task(SharedState sharedState){
this.sharedState = sharedState;
}
public void DoTheTask()
{
for (int i = 0; i < 50000; i++)
{
sharedState.State += 1;
}
}
}
在方法中~应建一个应象~应送应并个应Main()SharedState20Thread
象的造函。在应所有的应程后~构数启方法应入一循应~使另个个应程应于等待Main()20
状状写应~直到所有的应程都应行完应应止。应程应行完应后~把共享应的合应应入控制台。因应应行
了个循应~有个写应程~所以入控制台的应应是。但是~事应50000201000000
常常非如此。并
class Program
{
static void Main()
{
265
第?部分 基 应 应
int numThreads = 20;
SharedState state = new SharedState();
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++)
{
threads[i] = new Thread(new Task(state).DoTheTask);
threads[i].Start();
}
for (int i = 0; i < numThreads; i++)
{
threads[i].Join();
}
Console.WriteLine("summarized {0}", state.State);
}
}
}
多次行应用程序的应果如下所示,运
summarized 939270
summarized 993799
summarized 998304
summarized 937630
每次行的应果都不同~但有一应果是正的。应应运没个确区很版本和应布版本的应应大。所使用的应型不同~应果也不一应。如果循应次改应比应将数会确小的应~就多次得到正的应~CPU
但不是每次。应应用程序非常个很很确小~容易看出应应~但应应应的原因在大型应用程序中就应应定。
必应在应程序中个添加同步功能~应可以用应应字应应。lock
用应句定应的应象表示~要等待指定应象的应定解除。只能应送引用应型。应定应应型只lock
是应定了一个没会个副本~应是有什应意应的。应应器提供一应定应应型的应应。应行了应定后—— 只有一应程得到了应定应~就可以行个运应句应。在应句应的最后~应象的应定被解除~一另locklock
个等待应定的应程就可以应得应应定应了。
lock (obj)
{
// synchronized region
}
要应定应成应~可以把应定静放在应型上,object
266
第18章 应程和同步lock (typeof(StaticClass))
{
}
使用应应字可以应的应例成应应置应应程安全。应应~一次只有一应程能应应应应例的将个lock
和方法。DoThis()DoThat()
public class Demo
{
public void DoThis()
{
lock (this)
{
// only one thread a time can access the DoThis and DoThat
methods
}
}
public void DoThat()
{
lock (this)
{
}
}
}
但是~因应应例的应象也可以用于外部的同步应应~我应不能在应中控制应应应应~所以应采用模式。在模式中~应建了一个私有应象~应应应象用于将个应SyncRootSyncRootsyncRootlock
句。
public class Demo
{
private object syncRoot = new object();public void DoThis()
{
lock (syncRoot)
{
// only one thread a time can access the DoThis and DoThat
methods
}
}
267
第?部分 基 应 应
public void DoThat()
{
lock (syncRoot)
{
}
}
}
使用应定是需要应应的~且不应是必需的。可以应建应的并两个个版本~一同步版本~一个异步版本。应里用修改应来演示。应本身并不是同步的~应可以在DemoDemoDoThis()
和方法中看出。应应应定应了属从属性~客应可以应应性中应得应的同步应应DoThat()IsSynchronized信息。应了应得应应的同步版本~可以使用应方法静应送一非同步应象~应个个Synchronized()方法返回会应型的应象。应应应派生自基应的一个内SynchronizedDemoSynchronizedDemoDemo部应~重了基应中的成应。重的成应使用了并写虚写模式。SyncRoot
public class Demo
{
private class SynchronizedDemo : Demo
{
private object syncRoot = new object();
private Demo d;
public SynchronizedDemo(Demo d)
{
this.d = d;
}
public override bool IsSynchronized
{
get { return true; }
}
public override void DoThis()
{
lock (syncRoot)
{
d.DoThis();
}
}
public override void DoThat()
{
268
第18章 应程和同步
lock (syncRoot)
{
d.DoThat();
}
}
}
public virtual bool IsSynchronized{
get { return false; }
}
public static Demo Synchronized(Demo d){
if (!d.IsSynchronized)
{
return new SynchronizedDemo(d);
}
return d;
}
public virtual void DoThis(){
}
public virtual void DoThat(){
}
}
必应注意~在使用应应~只有方法是同步的~应应应的成应的应个两个SynchronizedDemo
用有同步。并没
警告,
模式可能使应程安全应生应面影响。集合应应应了模SyncRoot.NET 1.0SyncRoot
式~的泛型集合应不再应应应模式。个.NET 2.0
下面研究一下前面的例子。如果应应用模式应定应性的应应~使属应应SyncRootSharedState
成应程安全的~仍出应前面会条描述的应应件。
public class SharedState
{
private int state = 0;
269
第?部分 基 应 应
private object syncRoot = new object();
public int State // there’s still a race condition, don’t do this!
{
get { lock (syncRoot) {return state; }}
set { lock (syncRoot) {state = value; }}
}
}
应用方法的应程应应应的存取器~以应得的前应当~DoTheTask()SharedStategetstate
接着存取器应应置新应。在应用应象的和存取器期应~应象有应定~一应没另个getstategetset
程可以应得应应应。
public void DoTheTask()
{
for (int i = 0; i < 50000; i++){
sharedState.State += 1;
}
}
所以~最好不改应应~应有应程安全性。它没SharedState
public class SharedState
{
private int state = 0;
public int State
{
get { return state; }
set { state = value; }}
}
然后在方法中~将应句添加到合适的地方,DoTheTask()lock
public void DoTheTask()
{
for (int i = 0; i < 50000; i++){
lock (sharedState)
{
270
第18章 应程和同步
sharedState.State += 1;
}
}
}
应应~应用程序的应果就应是正的,确
summarized 1000000
警告,
在一地方使用个应句不意并个味着~应应应象的其他应程都在等待。必应应每应应共享lock
状应的应程应式使用同步功能。
当然~应必应修改应的应应~应将个个增提供应一原子操作。应是一应应应应—— 什应SharedState
是应的原子功能,
public class SharedState{
private int state = 0;
private object syncRoot = new object();
public int State
{
get { return state; }}
public int IncrementState(){
lock (syncRoot)
{
return ++state;
}
}
}
提示,
上面应定应应状个个增的最后一例子~有一使用应的版本更快~如下所示。Interlocked
18.6.2 Interlocked
应用于使应量的应应应句原子化。不是应程安全的~的操作包括它从内Interlockedi++
271
第?部分 基 应 应
存中应取一应~应应应应个增~再将它内会断存应回存。应些操作都可能被应程应度器打。1
应提供了以应程安全的方式应增、应和减交应应的方法。Interlocked
应提供的方法如表所示。Interlocked18-1
表 18-1
Interlocked应的成应应 明Increment()Increment()方法应增一应量~把应果存应到一原子操作中个个
Decrement()Decrement()应一应量~存应应果减个并
Exchange()Exchange()将个并一应量应置应指定的应~返回应量的初始应CompareExchange()CompareExchange()应应量应行相等比应~如果应相等~就应置指定的应两个它~
返回初始应
Add()Add()应应应行相两个个加操作~用应果替代第一应量Read()Read()方法用于在一原子操作中存中应取个从内64位应。在32位系应中~应
取64位不是原子化的~而需要存地址中应取从两个内
在位系应中~不需要方法~因应应应位是一原子操作个64Read()64
与其他同步技应相比~使用应快得多。但是~只能用于应应的同步应会它Interlocked应。
例如~应里不使用应句应定应应量的应应~把应置应一新应~以它个它防是locksomeState空的~而可以使用应~比应快,它Interlocked
lock (this)
{
if (someState == null)
{
someState = newState;
}
}
应功能相同、但比应快的个版本使用了方法,Interlocked.CompareExchange
Interlocked.CompareExchange
(ref someState, newState, null);
不在应句中应行应增操作,lock
public int State
{
get
{
lock (this)
{
272
第18章 应程和同步
return ++state;
}
}
}
而使用应快的,Interlocked.Increment()public int State
{
get
{
return Interlocked.Increment(ref state);
}
}
应18.6.3 Monitor
的应句由应应器解析应使用应。下面的应句,C#lockMonitorlocklock (obj)
{
// synchronized region for obj}
解析应应用方法~应方法一直等待~直到应程应得应象的应定应止。一次只有一会个Enter()
应程能成应应象应定的应有者。只要解除了应定~应程就可以应入同步段。应的方MonitorExit()
法解除了应定。无应在什应情下解除应应定况异况;包括抛出常的情,~方法都放在Exit()
应的应理程序中。tryfinally
提示,
应应第章。try/finally13
Monitor.Enter(obj);
try
{
// synchronized region for obj}
finally
{
Monitor.Exit(obj);
}
273
第?部分 基 应 应
与的应句相比~应的主要应点是,可以添加一等待应得应定的超应应个。C#lockMonitor
应应就不无会限期地等待应得应定~而可以使用方法~应应送一超应应~定等待应它个确TryEnter
得应定的最应应应。如果得到了的应定~方法就返回~应应由应象应定的objTryEntertrueobj状另个应。如果一应程应定的应应超应了毫秒~方法就返回~应程不再obj500TryEnterfalse等待~而是应行其他操作。也应在以后~应应程应应会再次应得应应定。
if (Monitor.TryEnter(obj, 500))
{
try
{
// acquired the lock
// synchronized region for obj
}
finally
{
Monitor.Exit(obj);
}
}
else
{
// didn’t get the lock, do something else
}
等待句柄18.6.4
是一个个号号抽象基应~用于等待一信的应置。可以等待不同的信~因应WaitHandle
是一基应~可以中个从派生一些应。WaitHandle
在本章前面使用步委托应~异已应使用了。步委托的方法异WaitHandleBeginInvoke()
返回一应应了个接口的应象。使用接口~可以用性属IAsycResultIAsycResultAsycWaitHandle
应应。在应用方法应~应程等待接收一等待句柄相应的信。会个与号WaitHandleWaitOne()
static void Main()
{
TakesAWhileDelegate d1 = TakesAWhile;
IAsyncResult ar = d1.BeginInvoke(1, 3000, null, null);
while (true)
{
Console.Write(".");
if (ar.AsyncWaitHandle.WaitOne(50, false))
274
第18章 应程和同步
{
Console.WriteLine("Can get the result now");
break;
}
}
int result = d1.EndInvoke(ar);
Console.WriteLine("result: {0}", result);
}
应定应的、应行等待的方法如表所示。WaitHandle18-2
表 18-2
WaitHandle应的成应应 明
WaitOne()WaitOne()是一应例方法~利用可以等待一信的应生。也可以应最个它个号
大等待应应指定一超应应个
WaitAll()WaitAll()是一应方法~用于应送个静WaitHandle应象的应~等待所有的数并
句柄应出信号
WaitAny()WaitAny()是一应方法~用于应送个静WaitHandle应象的应~等待其中数并
一句柄应出信。应方法返回应出信的等待句柄应象的索引~以便个号个号确
定可以在程序中应应应行什应功能。如果在句柄应出信之前超应~号WaitAny()
就返回WaitTimeout
使用属将个内个并性~应可以一置句柄应予一操作系应应源~等待应句SafeWaitHandle
柄。例如~可以指定一个等待文件操作的完成~或者指定定制的SafeWaitHandleI/O
~应第参章。SafeTransactionHandle21
应、和派生自基应~所以可以在等待应使用应它。MutexEventSemaphoreWaitHandle
应18.6.5 Mutex
~互斥是中提供同步应应多应程的一应个个。Mutex(mutual exclusion).NET Framework它非常应似于应~因应应都只有一应程能应有应定。只有一应程能应得它个个互斥应定~应Monitor
应受互斥应定保应的同步代应区域。
在应的造函中~可以指定构数互斥应定是否最初应由应用应程应有~定应互斥应定的Mutex
名~应得称个参数参数互斥应定是否已存在的信息。在下面的示例代应中~第三定应应应出~接收一个表示互斥应定是否应新应建的布应应。如果返回的应是~就表示互斥应定已应定应。互false
斥应定可以在一应程中定应~因应操作系应知另个称它道有名的互斥应定~由不同的应程共享。如果有应没称互斥应定指定名~互斥应定就是未命名的~不在不同的应程之应共享。
bool createdNew;
Mutex mutex = new Mutex(false, "ProCSharpMutex", out createdNew);
275
第?部分 基 应 应
要打应已有的互斥应定~应可以使用方法~不需要用造函它构Mutex.OpenExisting()
数应建互斥应定应需要的应限。.NET
应派生自基应~因此可以利用方法应得互斥应定~在应应程MutexWaitHandleWaitOne()
中成应应互斥应定的应有者。应用方法~可应即放互斥应定。ReleaseMutex()if (mutex.WaitOne())
{
try
{
// synchronized region
}
finally
{
mutex.ReleaseMutex();
}
}
else
{
// some problem happened while waiting
}
由于系应知道有名的称它启两互斥应定~因此可以使用禁止应用程序应应次。在下面的
窗体应用程序中~应用了应象的造函。接构数称着应应名应WindowsMutex
的互斥应定是否存在。如果存在~应用程序就退出。SingletonWinAppMutex
static class Program
{
[STAThread]
static void Main()
{
bool createdNew;
Mutex mutex = new Mutex(false, "SingletonWinAppMutex", out
createdNew);
if (!createdNew)
{
MessageBox.Show("You can only start one instance of the
application");
Application.Exit();
return;
}
276
第18章 应程和同步
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
应18.6.6 Semaphore
旗应应定非常应似于互斥应定~其应是~区个旗应应定可以同应由多应程使用。(Semaphore)
旗应应定是一应应的数个数互斥应定。使用旗应应定~可以定应允应同应应应受旗应应定保应的应源的应程。如果有应多应源~且只允应一定量的应程应应应应源~就可以使用数旗应应定。例如~要应应系应上的物理端口~且有三端口可用~就个个允应三应程同应应应端口~但第四应程需要等个I/OI/O
待前三应程中的一应个个放应源。
在下面的示例程序中~方法应建了个个数应程和一应应应的旗应应定。在Main()64
应的造函中~定应了应定的应~可以用构数数数它旗应应定第二个参数来应得~应Semaphore()定应了最初自由的应定数第一个参数。如果第一的应个参数个参数它小于第二~应的差就()
是已应应予应程的旗应应定。数与称互斥应定一应~也可以应旗应应定指定名~使之在不同的应程之应共享。应里定应旗应应定应有指定名~所以只能在应应程中使用。在应建了没称它个应Semaphore象之后~应启个它六应程~应都应得了相同的旗应应定。
using System;
using System.Threading;
using System.Diagnostics;
namespace Wrox.ProCSharp.Threading
{
class Program
{
static void Main()
{
int threadCount = 6;
int semaphoreCount = 4;
Semaphore semaphore = new Semaphore(semaphoreCount,
semaphoreCount);
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++)
{
threads[i] = new Thread(ThreadMain);
threads[i].Start(semaphore);
277
第?部分 基 应 应
}
for (int i = 0; i < threadCount; i++)
{
threads[i].Join();
}
Console.WriteLine("All threads finished");
}
在应程的主方法中~应程利用应定了旗应。旗应应定的应是数ThreadMain()WaitOne()
~所以有四应程可以应得应定。应程个必应等待~应里应定应了最大等待应应应毫秒。如果45500
在应等待应应应后未能应得应定~应程就把一信息入控制台~在循应中应应等待。只要应得了应定个写~
应程就把一信息入控制台~个写睡眠一段应应~然后解除应定。在解除应定应~一定要解除应源
的应定。应就是在应理程序中应用应的方法的原因。finallySemaphoreRelease()
static void ThreadMain(object o)
{
Semaphore semaphore = o as Semaphore;Trace.Assert(semaphore != null, "o must be a Semaphore type");
bool isCompleted = false;
while (!isCompleted)
{
if (semaphore.WaitOne(600, false))
{
try
{
Console.WriteLine("Thread {0} locks the sempahore",
Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
}
finally
{
semaphore.Release();
Console.WriteLine("Thread {0} releases the semaphore",
Thread.CurrentThread.ManagedThreadId);
isCompleted = true;
}
}
else
{
278
第18章 应程和同步
Console.WriteLine("Timeout for thread {0}; wait again",
Thread.CurrentThread.ManagedThreadId);
}
}
}
}
}
运个行应用程序~可以看到有四应程应得了应定。应和的应程需要等待。应等待ID78
会个重应应行~直到四应得应定的应程之一解除了旗应应定。
Thread 3 locks the sempahore
Thread 4 locks the sempahore
Thread 5 locks the sempahore
Thread 6 locks the sempahore
Timeout for thread 8; wait again
Timeout for thread 7; wait again
Timeout for thread 8; wait again
Timeout for thread 7; wait again
Timeout for thread 7; wait again
Timeout for thread 8; wait again
Thread 3 releases the semaphore
Thread 8 locks the sempahore
Thread 4 releases the semaphore
Thread 7 locks the sempahore
Thread 5 releases the semaphore
Thread 6 releases the semaphore
Thread 8 releases the semaphore
Thread 7 releases the semaphore
All threads finished
应18.6.7 Events
事件是一系应应的应源同步方法。应了在托管代应中使用系应事件~另个.NET
在命名空应中提供了和应。FrameworkSystem.ThreadingManualResetEventAutoResetEvent提示,
第章介应了中的应应字~它与命名空应中的应7C#eventSystem.ThreadingEvent
没有应系。应应字基于委托~而上述两个应是封装内器~用于系应应的置eventEvent.NET
事件应源的同步。
279
第?部分 基 应 应
可以使用事件通知其他应程,应里有一些据~完成了一些操作等。事件可以应信数号~
也可以不应信。使用前面介应的号应~应程可以等待应于应信应的事件。号状WaitHandle
应用方法~可使即应信。应用号方法~可以使之返回不Set()ManualResetEventReset()应信的应。如果多应程等待一事件应出信~应用了号状个个号并方法~就应放所有等待的应Set()程。外~如果一应程应应应用了另个方法~但事件已应应出了信~等待的应程就可号WaitOne()
以应应等待。
也是通应方法应信。也可以使用号方法使之返回不应信AutoResetEventSet()Reset()号状个号当个的应。但是~如果一应程在等待自应重置的事件应信~第一应程的等待应束应~应事件自应应应不应信的应。应应~如果多应程在等待事件应信~就只有一应程应束其等待会号状个号个
状它应~不是等待应应最应的应程~而是应先应最高的应程。
应了演示应的事件~下面的应定应了方法~AutoResetEventThreadTaskCalculation()
应是应程的入口点。在应方法中~应程接收用于应个数构算的应入据;由应定应,~应将InputData果入应量写~可以通应它属来随性应应。只要完成了应算;在机的一段应应应后,~resultResult
就应用的方法~向事件应信。号AutoResetEventSet()
public struct InputData
{
public int X;
public int Y;
public InputData(int x, int y)
{
this.X = x;
this.Y = y;
}
}
public class ThreadTask
{
private AutoResetEvent autoEvent;
private int result;
public int Result
{
get { return result; }
}
public ThreadTask(AutoResetEvent ev)
{
this.autoEvent = ev;
}
280
第18章 应程和同步public void Calculation(object obj)
{
InputData data = (InputData)obj;
Console.WriteLine("Thread {0} starts calculation",
Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(new Random().Next(3000));
result = data.X + data.Y;
// signal the event - completed!
Console.WriteLine("Thread {0} is ready",
Thread.CurrentThread.ManagedThreadId);
autoEvent.Set();
}
}
程序的方法定应了包含四个应象的应和包含四数个应Main()AutoResetEventThreadTask
象的应。每数个在造函中用一构数个应象初始化~应应每应程在个ThreadTaskAutoResetEvent
完成应都有自己的事件应象应信。应在使用来号应应用方法~ThreadPoolQueueUserWorkItem()
应后台应程应行应算任应。
class Program
{
static void Main()
{
int taskCount = 4;
AutoResetEvent[] autoEvents = new AutoResetEvent[taskCount];
ThreadTask[] tasks = new ThreadTask[taskCount];
for (int i = 0; i < taskCount; i++)
{
autoEvents[i] = new AutoResetEvent(false);
tasks[i] = new ThreadTask(mevents[i]);
ThreadPool.QueueUserWorkItem(tasks[i].Calculation,
new InputData(i + 1, i + 3));
}
//...
应应在用于等待应中的任意一事件。数个等待向任意一事件个WaitHandleWaitAny()
应信。号从返回的匹配数应中应送应的事件~以提供向哪个事WaitAny()indexWaitAny()
件应信的信息~应应事件中应取应果。号并从个
281
第?部分 基 应 应
for (int i = 0; i < taskCount; i++)
{
int index = WaitHandle.WaitAny(autoEvents);
if (index == WaitHandle.WaitTimeout)
{
Console.WriteLine("Timeout!!");
}
else
{
Console.WriteLine("finished task for {0}, result: {1}",
index,
tasks[index].Result);
}
}
}
}
启它应应用程序~可以看到应程在应行应算~应置事件~通知主应程~可以应取应果了。由于机应应、是应应随会数版本应是应布版本、以及硬件的不同~看到不同的应序~应程池中有不同量的应程在应行任应。应里重用了应程池中的应程~完成了任应~因应比应快~能第一两个它个4
完成应算。
Thread 3 starts calculation
Thread 4 starts calculation
Thread 5 starts calculation
Thread 4 is ready
finished task for 1, result: 6
Thread 4 starts calculation
Thread 3 is ready
finished task for 0, result: 4
Thread 4 is ready
finished task for 3, result: 10
Thread 5 is ready
finished task for 2, result: 8
应元18.7 COM
应程应是一个与应象相应的重要主应。定应了应元模型。在应应程应元COMCOM(STA)中~运会行应应行同步。多应程应元的性能比应好~但有没运行应的同步COM(MTA)COM功能。
282
第18章 应程和同步
应件在注册个从它表中应置了一配置应~而定应了需要的应元模型。以应程安全的COM
方式应应的应件支持。多应程可以同应应应应应件~应应件必应自己应应同步。不能应个个COMMTA
理多应程的个应件需要使用。在中~只有一应程个应是应应应应应件。另COMSTASTA()一应程只有使用代理~应应接到个应象上的应程应送一个信息~才能应应应件。COMWindows
使用应信息应行同步。STAWindows
应件应支持模型。用应应配置的应件支持和。VB6STAbothCOMSTAMTA
应件定应了应应元的要求~而应例化应象的应程定应了行的应元。应应元运它个COMCOM
应应就是需要的应元。COM
应程默应行在运上。在应用程序的方法中~有应可以看.NETMTAWindowsMain()到特性。应特性指定~主应程个加入。窗体个应用程序需要一[STAThread]STAWindows
应程。STA
[STAThread]
static void Main()
{
//...
在应建新应程应~可以将或特性应用于应程的入口点方法~[STAThread][MTAThread]或应用应的方法~定应应元模型~之后应应程,来启ThreadSetApartmentTherad()
Thread t1 = new Thread(DoSomeWork);
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
使用方法可以应得应程的应元。GetApartmentThread()
提示,
第章介应了与应件的交互操作和应元模型的应应容。内23.NETCOMCOM
应件18.8 BackgroundWorker
如果有应应没写应用程序~就可以跳应应一应~应应应应后面的容。注意~在内Windows
应用程序中使用应程会增加应应性~应在应应了窗体的章应第,章或WindowsWindows(2830)
第章后~再应应本应。无应如何~从窗体来的角度看~应里演示的WPF(31)Windows
窗体应用程序都是非常应应的。Windows
窗体和控件应定到一应程上。应于每控件~都只能应建应控件的应个个从WindowsWPF
程中应用方法。也就是应~如果有一后台应程~就不能直接在应应程中应应个个控件。UI
在窗体从控件中~唯一可以非应建应程中应用的是方法Windows
、、和性属。和Invoke()BeginInvoke()EndInvoke()InvokeRequiredBeginInvoke()
是的步异会个版本。应些方法切应到应建控件的应程上~应用应予一委托EndInvoke()Invoke()
参数参数并的方法~应委托可以应送应应些方法。应些方法的使用不应应~应就是新应件.NET 2.0
283
第?部分 基 应 应
和新步模式一起应应的原因。异BackgroundWorker
应定应了如表所示的方法、性和事件。属BackgroundWorker18-3
表 18-3
BackgroundWorker应的成应应明
IsBusy在激活异属步任应应~性IsBusy返回true
CancellationPending在应用CancelAsync()方法后~性属CancellationPending返回true。如
果应性应置应个属true~步任应就应停止其工作异
RunWorkerAsync()方法RunWorkerAsync()引应DoWork事件~在一应的应程中应应个独启
DoWork异步任应
CancelAsync()如果用了取消功能启(将WorkerSupportCancellation属性应置应
WorkerSupportCancellationtrue)~就可以用CancelAsync()方法取消步任应异
ReportProgress()如果WorkerReportsProgress属性应置应true~BackgroundWorker就可
ProgressChanged以应出步任应应度的应应异反应信息。应用ReportProgress()方法~步任应异
WorkerReportsProgress可以提供了已完成的工作百分数个会反应信息~之后应方法引应
ProgressChanged事件
RunWorkerCompleted无应取消否~只要完成了步任应~就引应与异RunWorkerCompleted事
件
下面的示例程序演示了控件在窗体应用程序中的用法~BackgroundWorkerWindows它个个应行一需要一定应应的任应。应建一新的窗体窗体个应用程序~在上添加三应应Windows
控件、三文本控件、个框两个个条个按应控件、一应度控件和一控件~如BackgroundWorker应所示。18-3
应 18-3
按表配置控件的性。属18-4
284
第18章 应程和同步
表 18-4
控 件性和事件属应
TextX:应应
Nametextbox文本框
应表()
控 件性和事件属应
TextY:应应
NametextBoxY文本框
TextResult:应应
NametextBoxResult文本框
NamebuttonCalculate按应
TextCalculate
ClickOnCalculate
NamebuttonCancel按应
TextCancel
EnabledFalse
ClickOnCancel
NameProgressBar应度条
BackgroundWorkerNamebackgroundWorker
DoWorkOnDoWork
RunWorkerCompletedOnWorkCompleted
在应目中添加应构。应应用于包含文本控件中的应入应据。个构框数CalcInput
public struct CalcInput
{
public CalcInput(int x, int y){
this.x = x;
this.y = y;
}
public int x;
public int y;
}
方法是按应控件的事件应理程序。在应行应程中~OnCalculate()buttonCalculateClick
按应被禁用~所以在应算完成之前~用应不能再次应应应按应。要应启buttonCalculate
285
第?部分 基 应 应
~可应用方法。使用应程池中的一BackgroundWorkerRunWorkerAsync()BackgroundWorker个来应程应算。需要应入应送应将参数事件的应理程序。RunWorkerAsync()DoWork
private void OnCalculate(object sender, EventArgs e)
{
this.buttonCalculate.Enabled = false;
this.textBoxResult.Text = String.Empty;
this.buttonCancel.Enabled = true;
this.progressBar.Value = 0;
backgroundWorker.RunWorkerAsync(new CalcInput(
int.Parse(this.textBoxX.Text), int.Parse(this.textBoxY.Text)));
}
方法应接到控件的事件上。在OnDoWork()BackgroundWorkerDoWork
中~通应性属接收应入。其应行代应模应的功能需要一定的参数DoWorkEventArgsArgument
应行应应和秒的睡眠应应。在睡眠应应应后~应将写算的应果入的属性。如5DoEventArgsResult果应将算和睡眠操作添加到方法中~在用应应入应~应用程序就不能应OnCalculate()Windows得用应应入。但是~应里使用一应应的应程~用应界面仍是个独激活的。
private void OnDoWork(object sender, DoWorkEventArgs e)
{
CalcInput input = (CalcInput)e.Argument;
Thread.Sleep(5000);
e.Result = input.x + input.y;
}
方法完成后~控件就引应事OnDoWork()BackgroundWorkerRunWorkerCompleted件。方法与个从应事件相应。应里应参数OnWorkCompleted()RunWorkerCompletedEventArgs的属写框性中接收应果~应应果入文本控件中。在引应应事件应~Resultresult
控件把控制应会它交应应建的应程~所以不需要使用窗体控件的BackgroundWorkerWindows
方法~而可以直接应用窗体属控件的性和方法。InvokeWindows
private void OnWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.textBoxResult.Text = e.Result.ToString();
this.buttonCalculate.Enabled = true;
this.buttonCancel.Enabled = false;
this.progressBar.Value = 100;
}
286
第18章 应程和同步
应在可以应应应用程序~看看应算应程是否立于独应程~仍是激活的~可以窗体UIUI
四应移应。但是~取消和应度功能仍需要应应。条
激活取消功能18.8.1
要激活取消功能~以停止应程的行~必应把运控件的性属BackgroundWorker
应置应。接着~应应与按应的事件相应的WorkerSupportsCancellationtruebuttonCancelClick
应理程序。控件的方法可以取消正在应行的步异OnCancelBackgroundWorkerCancelAsync()
任应。
private void OnCancel(object sender, EventArgs e)
{
backgroundWorker.CancelAsync();
}
异会异步任应不自应取消。在应行步任应的应理程序中~必应修改其应应代应~OnDoWork()
应应控件的性属。应性在应用个属方法应应BackgroundWorkerCancellationPendingCancelAsync()置。如果要应行取消操作~就把的属性应置应~退出应理程序。DoWorkEventArgsCanceltrue
private void OnDoWork(object sender, DoWorkEventArgs e)
{
CalcInput input = (CalcInput)e.Argument;
for (int i = 0; i < 10; i++)
{
Thread.Sleep(500);
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
return;
}
}
e.Result = input.x + input.y;
}
如果步方法成功完成或异被取消~就应用完成应理程序。如果取OnWorkCompleted()消了应方法~就不能应应属会个性~因应应抛出一异并常~应ResultInvalidOperationException示操作被取消的信息。所以必应应应的属并性~根RunWorkerCompletedEventArgsCancelled据不同的情应行不同的操作,况
private void OnWorkCompleted(object sender,
287
第?部分 基 应 应
RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
this.textBoxResult.Text = "Cancelled";}
else
{
this.textBoxResult.Text = e.Result.ToString();}
this.buttonCalculate.Enabled = true;this.buttonCancel.Enabled = false;}
再次行应用程序~就可以在用应界面上取消步应程了。运异
激活应度功能18.8.2
应了应得用应界面的应度信息~必应将控件的BackgroundWorkerWorkerReports-
属性应置应。Progresstrue
在方法中~可以用方法应告OnDoWorkReportProgress()
控件的应度。BackgroundWorker
private void OnDoWork(object sender, DoWorkEventArgs e)
{
CalcInput input = (CalcInput)e.Argument;for (int i = 0; i < 10; i++)
{
Thread.Sleep(500);
backgroundWorker.ReportProgress(i * 10);
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
return;
}
}
e.Result = input.x + input.y;
}
方法引应控件的事件~应事件个ReportProgress()BackgroundWorkerProgressChanged
288
第18章 应程和同步
会将控件改应应程。UI
在事件中添加方法~在其应应代应中~应应度控条ProgressChangedOnProgressChanged()
件应置一个从的属性中接收的新应。ProgressChangedEventArgsProgressPercentageprivate void OnProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.progressBar.Value = e.ProgressPercentage;}
在事件应理程序中~应度最应应置应条。OnWorkCompleted()100%
private void OnWorkCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
this.textBoxResult.Text = "Cancelled";}
else
{
this.textBoxResult.Text = e.Result.ToString();}
this.buttonCalculate.Enabled = true;this.buttonCancel.Enabled = false;
this.progressBar.Value = 100;
}
应是正在应算的应用程序。18-4
应 18-4
289
第?部分 基 应 应
小应18.9
本章介应了如何通应命名空应应多应程应用程序。在应用程序中使用写System.Threading
多应程要仔应应。太多的应程应划会会致应源应应~应程不足又使应用程序应行应慢~应行效果也不好。
中的命名空应允应应理应程~但.NET FrameworkSystem.Threading.NET Framework并没有完成多应程中所有困应的任应。我应必应考应应程的应先应和同步应应。本章应应了应些应应~介应了如何在应用程序中应应应应。应应述了它与条死应和应应件相应的应应。C#
如果要在应用程序中使用多应程功能~就必应仔应应。划C#
下面是应于应程的一些应应,
应应使同步要求降到最少。同步是非常应应的~会运阻碍应程的行。如果应应避免共享?
状当并应~就可以避免同步。然~应不应是可行。
应的应成应应是应程安全的。静中的应通常是应应。?.NET Framework
应例应不需要是应程安全的。要应得最状佳的性能~最好根据需要在应的外部使用同?
步功能~且不应应的每成应使用应功能。个应的应例成应通常不是应程.NET Framework
安全的。中每应的应方面信息可以在个部分到。找FrameworkThread Safety
290