为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

c#线程和同步

2017-11-29 50页 doc 205KB 181阅读

用户头像

is_597436

暂无简介

举报
c#线程和同步c#线程和同步 18第 章 线程和同步 在应用程序中应行应应用需要一定的应应。用应不希望在安用应界面应只是等待~直到网装 服应器返回一应应止。用应可以在应应程中应行其他一些操作~甚至取消应送应服应器的应求个响个。应些都可以使用应程应应。来 使用应程有原因。不应用应等待是其中一原因。应于所有需要等待的操作~例如几个个 文件、据应或应应应的应都需要一定的应应~此应就可以应一新应程~完成其他任应。数网启启个即 使是应理密集型的任应~应程也是有助的。一应程的多应程可以同应行在不同的帮个个运上~CPU或多核心个的不同核心上。CPU ...
c#线程和同步
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
/
本文档为【c#线程和同步】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索