国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
.NET 線程池編程技術(shù)

介紹

         如果你有在任何編程語言下的多線程編程經(jīng)驗(yàn)的話,你肯定已經(jīng)非常熟悉一些典型的范例。通常,多線程編程與基于用戶界面的應(yīng)用聯(lián)系在一起,它們需要在不影響終端用戶的情況下,執(zhí)行一些耗時(shí)的操作。取出任何一本參考書,打開有關(guān)線程這一章:你能找到一個(gè)能在你的用戶界面中并行執(zhí)行數(shù)學(xué)運(yùn)算的多線程示例嗎?

我的目的不是讓你扔掉你的書,不要這樣做!多線程編程技術(shù)使基于用戶界面的應(yīng)用更完美。實(shí)際上, Microsoft .NET框架支持在任何語言編寫的窗口下應(yīng)用多線程編程技術(shù),允許開發(fā)人員設(shè)計(jì)非常豐富的界面,提供給終端用戶一個(gè)更好的體驗(yàn)。但是,多線程編程技術(shù)不僅僅是為了用戶界面的應(yīng)用,在沒有任何用戶界面的應(yīng)用中,一樣會(huì)出現(xiàn)多個(gè)執(zhí)行流的情況。

我們用一個(gè)“硬件商店”的客戶/服務(wù)器應(yīng)用系統(tǒng)作為例子??蛻舳耸鞘浙y機(jī),服務(wù)端是運(yùn)行在倉庫里一臺(tái)獨(dú)立的機(jī)器上的應(yīng)用系統(tǒng)。你可以想象一下,服務(wù)器沒有任何的用戶界面,如果不用多線程技術(shù)你將如何去實(shí)現(xiàn)?

服務(wù)端通過通道(http, sockets, files 等等)接收來自客戶端的請(qǐng)求并處理它們,然后發(fā)送一個(gè)應(yīng)答到客戶端。圖1顯示了它是如何運(yùn)作的。

1 單線程的服務(wù)端應(yīng)用系統(tǒng)

為了讓客戶端的請(qǐng)求不會(huì)遺漏,服務(wù)端應(yīng)用系統(tǒng)實(shí)現(xiàn)了某種隊(duì)列來存放這些請(qǐng)求。圖1顯示了三個(gè)請(qǐng)求同時(shí)到達(dá),但只有其中的一個(gè)被服務(wù)端處理。當(dāng)服務(wù)端開始執(zhí)行 "Decrease stock of monkey wrench," 這個(gè)請(qǐng)求時(shí),其它兩個(gè)必須在隊(duì)列中等待。當(dāng)?shù)谝粋€(gè)執(zhí)行完成后,接著是第二個(gè),以此類推。這種方法普遍用于許多現(xiàn)有的系統(tǒng),但是這樣做系統(tǒng)的資源利用率很低。假設(shè) decreasing the stock”請(qǐng)求修改磁盤上的一個(gè)文件,而這個(gè)文件正在被修改中,CPU將不會(huì)被使用,即使這個(gè)請(qǐng)求正處在待處理階段。這類系統(tǒng)的一個(gè)普遍特征就是低CPU利用時(shí)間導(dǎo)致出現(xiàn)很長(zhǎng)的響應(yīng)時(shí)間,甚至是在訪問壓力很大的環(huán)境里也這樣。

         另外一個(gè)策略就是在當(dāng)前的系統(tǒng)中為每一個(gè)請(qǐng)求創(chuàng)建不同的線程。當(dāng)一個(gè)新的請(qǐng)求到達(dá)之后,服務(wù)端為進(jìn)入的請(qǐng)求創(chuàng)建一個(gè)新線程,執(zhí)行結(jié)束時(shí),再銷毀它。下圖說明了這個(gè)過程:

         2:多線程服務(wù)端應(yīng)用系統(tǒng)

就像如圖2所示的那樣。我們有了較高的CPU利用率。即使它已經(jīng)不再像原來的那樣慢了,但創(chuàng)建線和銷毀程也不是最恰當(dāng)?shù)姆椒ā<僭O(shè)線程的執(zhí)行操作不復(fù)雜,由于需要花額外的時(shí)間去創(chuàng)建和銷毀線程,所以最終會(huì)嚴(yán)重影響系統(tǒng)的響應(yīng)時(shí)間。另外一點(diǎn)就是在壓力很大的環(huán)境下,這三個(gè)線程會(huì)給系統(tǒng)帶來很多的沖擊。多個(gè)線程同時(shí)執(zhí)行請(qǐng)求處理將導(dǎo)致CPU的利用率達(dá)到100%,而且大多數(shù)時(shí)間會(huì)浪費(fèi)在上下文切換過程中,甚至?xí)^處理請(qǐng)求的本身。這類系統(tǒng)的典型特征是大量的訪問會(huì)導(dǎo)致響應(yīng)時(shí)間呈指數(shù)級(jí)增長(zhǎng)和很高的CUP使用時(shí)間。

         一個(gè)最優(yōu)的實(shí)現(xiàn)是綜合前面兩種方案而提出的觀點(diǎn)----線程池(Thread Pool),當(dāng)一個(gè)請(qǐng)求達(dá)到時(shí),應(yīng)用系統(tǒng)把置入接收隊(duì)列,一組的線程從隊(duì)列提取請(qǐng)求并處理之。這個(gè)方案如下圖所示:

 

3:?jiǎn)⒂镁€程池的服務(wù)端應(yīng)用系統(tǒng)

在這個(gè)例子中,我們用了一個(gè)含有兩個(gè)線程的線程池。當(dāng)三個(gè)請(qǐng)求到達(dá)時(shí),它們立刻安排到隊(duì)列等待被處理,因?yàn)閮蓚€(gè)線程都是空閑的,所以頭兩個(gè)請(qǐng)求開始執(zhí)行。當(dāng)其中任何一個(gè)請(qǐng)求處理結(jié)束后,空閑的線程就會(huì)去提取第三個(gè)請(qǐng)求并處理之。在這種場(chǎng)景中,系統(tǒng)不需要為每個(gè)請(qǐng)求創(chuàng)建和銷毀線程。線程之間能互相利用。而且如果線程池的執(zhí)行高效的話,它能增加或刪除線程以獲得最優(yōu)的性能。例如當(dāng)線程池在執(zhí)行兩個(gè)請(qǐng)求時(shí),而CPU的利用率才達(dá)到50%,這表明執(zhí)行請(qǐng)求正等待某個(gè)事件或者正在做某種I/O操作。線程池可以發(fā)現(xiàn)這種情況,并增加線程的數(shù)量以使系統(tǒng)能在同一時(shí)間處理更多的請(qǐng)求。相反的,如果CPU利用率達(dá)到100%,線程池可以減少線程的數(shù)量以獲得更多的CPU時(shí)間,而不要浪費(fèi)在上下文切換上面。

 

.NET中的線程池

         基于上面的例子,在企業(yè)級(jí)應(yīng)用系統(tǒng)中有一個(gè)高效執(zhí)行的線程池是至關(guān)重要的。Microsoft.NET框架的開發(fā)環(huán)境中已經(jīng)實(shí)現(xiàn)了這個(gè),該系統(tǒng)的核心提供了一個(gè)現(xiàn)成可用的最優(yōu)線程池。

這個(gè)線程池不僅對(duì)應(yīng)用程序可用,而且還融合到框架中的多數(shù)類中。.NET 建立在同一個(gè)池上是一個(gè)很重要的功能特性。比如 .NET Remoting 用它來處理來自遠(yuǎn)程對(duì)象的請(qǐng)求。

         當(dāng)一個(gè)托管應(yīng)用程序開始執(zhí)行時(shí),運(yùn)行時(shí)環(huán)境(runtime)提供一個(gè)線程池,它將在代碼第一次訪問時(shí)被創(chuàng)建。這個(gè)池與應(yīng)用程序所在運(yùn)行的物理進(jìn)程關(guān)聯(lián)在一起,當(dāng)你用.NET框架下的同一進(jìn)程中運(yùn)行多個(gè)應(yīng)用程序的功能特性時(shí)(稱之為應(yīng)用程序域),這將是一個(gè)很重要的細(xì)節(jié)。在這種情況下,由于它們都使用同樣的線程池,一個(gè)壞的應(yīng)用程序會(huì)影響進(jìn)程中的其它應(yīng)用程序。

         你可以通過System.Threading 名稱空間的Thread Pool 類來使用線程池,如果你查看一下這個(gè)類,就會(huì)發(fā)現(xiàn)所有的成員都是靜態(tài)的,而且沒有公開的構(gòu)造函數(shù)。這是有理由這樣做的,因?yàn)槊總€(gè)進(jìn)程只有一個(gè)線程池,并且我們不能創(chuàng)建新的。這個(gè)限制的目的是為了把所有的異步編程技術(shù)都集中到同一個(gè)池中。所以我們不能擁有一個(gè)通過第三方組建創(chuàng)建的無法管理的線程池。

 

線程池中執(zhí)行的函數(shù)

ThreadPool.QueueUserWorkItem 方法運(yùn)行我們?cè)谙到y(tǒng)線程池上啟動(dòng)一個(gè)函數(shù),它的聲明如下:

public static bool QueueUserWorkItem (WaitCallback callBack, object state)
第一個(gè)參數(shù)指明我們將在池中執(zhí)行的函數(shù),它的聲明必須與WaitCallback代理(delegate)互相匹配:public delegate void WaitCallback (object state);

State 參數(shù)允許任何類型的信息傳遞到該方法中,它在調(diào)用QueueUserWorkItem時(shí)傳入。

 

讓我們結(jié)合這些新概念,看看“硬件商店”的另一個(gè)實(shí)現(xiàn)。

using System;
using System.Threading;
namespace ThreadPoolTest
{
   class MainApp
   {
      static void Main()
      {
         WaitCallback callBack;
         callBack = new WaitCallback(PooledFunc);
         ThreadPool.QueueUserWorkItem(callBack,
            "Is there any screw left?");
         ThreadPool.QueueUserWorkItem(callBack,
            "How much is a 40W bulb?");
         ThreadPool.QueueUserWorkItem(callBack,
            "Decrease stock of monkey wrench");   
         Console.ReadLine();
      }
 
      static void PooledFunc(object state)
      {
         Console.WriteLine("Processing request '{0}'", (string)state);
         // Simulation of processing time
         Thread.Sleep(2000);
         Console.WriteLine("Request processed");
      }
   }
}

為了簡(jiǎn)化例子,我們?cè)?/span>Main 類中創(chuàng)建一個(gè)靜態(tài)方法用于處理請(qǐng)求。由于代理的靈活性,我們可以指定任何實(shí)例方法去處理請(qǐng)求,只要這些方法的聲明與代理相同。在這里范例中,通過調(diào)用Thread.Sleep,實(shí)現(xiàn)延遲兩秒以模擬處理時(shí)間。

你如果編譯和執(zhí)行這個(gè)范例,將會(huì)看到下面的輸出:

Processing request 'Is there any screw left?'
Processing request 'How much is a 40W bulb?'
Processing request 'Decrease stock of monkey wrench'
Request processed
Request processed
Request processed

 

注意,所有的請(qǐng)求都被不同的線程并行處理了。

我們可以通過在兩個(gè)方法中加入如下的代碼,以此看到更多的信息。

  // Main method
   Console.WriteLine("Main thread. Is pool thread: {0}, Hash: {1}",
            Thread.CurrentThread.IsThreadPoolThread, 
            Thread.CurrentThread.GetHashCode());
   // Pool method
   Console.WriteLine("Processing request '{0}'." + 
      " Is pool thread: {1}, Hash: {2}",
      (string)state, Thread.CurrentThread.IsThreadPoolThread, 
      Thread.CurrentThread.GetHashCode());
 

我們?cè)黾恿艘粋€(gè)Thread.CurrentThread.IsThreadPoolThread的調(diào)用。如果目標(biāo)線程屬于線程池,這個(gè)屬性將返回True。另外,我們還顯示了用GetHashCode 方法從當(dāng)前線程返回的結(jié)果。它是唯一標(biāo)識(shí)當(dāng)前執(zhí)行線程的值?,F(xiàn)在看一看這個(gè)輸出結(jié)果:

Main thread. Is pool thread: False, Hash: 2
Processing request 'Is there any screw left?'. Is pool thread: True, Hash: 4
Processing request 'How much is a 40W bulb?'. Is pool thread: True, Hash: 8
Processing request 'Decrease stock of monkey wrench '. Is pool thread: True, Hash: 9
Request processed
Request processed
Request processed
 

你可以看到所有的請(qǐng)求都被系統(tǒng)線程池中的不同線程執(zhí)行。再次運(yùn)行這個(gè)例子,注意系統(tǒng)CPU的利用率,如果你沒有任何其它應(yīng)用程序在后臺(tái)運(yùn)行的話,它幾乎是0%。因?yàn)橄到y(tǒng)唯一正在做的是每執(zhí)行2秒后就掛起的處理。

         我們來修改一下這個(gè)應(yīng)用,這次我們不掛起處理請(qǐng)求的線程,相反我們會(huì)一直讓系統(tǒng)忙,為了做到這點(diǎn),我們用Environment.TickCount. 構(gòu)建一個(gè)每隔兩秒就對(duì)請(qǐng)求執(zhí)行一次的循環(huán)。

int ticks = Environment.TickCount;
while(Environment.TickCount - ticks < 2000);

現(xiàn)在打開任務(wù)管理器,看一看CPU的使用率,你將看到應(yīng)用程序占有了CPU100%的使用率。再看一下我們程序的輸出結(jié)果:

Processing request 'Is there any screw left?'. Is pool thread: True, Hash: 7
Processing request 'How much is a 40W bulb?'. Is pool thread: True, Hash: 8
Request processed
Processing request 'Decrease stock of monkey wrench '. Is pool thread: True, Hash: 7
Request processed
Request processed
 

注意第三個(gè)請(qǐng)求是在第一個(gè)請(qǐng)求處理結(jié)束之后執(zhí)行的,而且線程的號(hào)碼仍然用原來的7,這個(gè)原因是線程池檢測(cè)到CPU的使用率已經(jīng)達(dá)到100%,一直等待某個(gè)線程空閑。它并不會(huì)重新創(chuàng)建一個(gè)新的線程,這樣就會(huì)減少線程間的上下文切換開銷,以使總體性能更佳。

 

使用定時(shí)器

假如你曾經(jīng)開發(fā)過Microsoft Win32的應(yīng)用程序,你知道SetTimer函數(shù)是API之一,通過這個(gè)函數(shù)可以指定的一個(gè)窗口接收到來自系統(tǒng)時(shí)間周期的WM_TIMER消息。用這個(gè)方法遇到的第一個(gè)問題是你需要一個(gè)窗口去接收消息,所以你不能用在控制臺(tái)應(yīng)用程序中。另外,基于消息的實(shí)現(xiàn)并不是非常精確,假如你的應(yīng)用程序正在處理其它消息,情況有可能更糟糕。

相對(duì)基于Win32的定時(shí)器來說, .NET 中一個(gè)很重要的改進(jìn)就是創(chuàng)建不同的線程,該線程阻塞指定的時(shí)間,然后通知一個(gè)回調(diào)函數(shù)。這里的定時(shí)器不需要Microsoft的消息系統(tǒng),所以這樣就更精確,而且還能用于控制臺(tái)應(yīng)用程序中。以下代碼顯示了這個(gè)技術(shù)的一種實(shí)現(xiàn):

class MainApp
{
   static void Main()
   {
      MyTimer myTimer = new MyTimer(2000);
      Console.ReadLine();
   }
}
class MyTimer
{
   int m_period;
   public MyTimer(int period)
   {
      Thread thread;
      m_period = period;
      thread = new Thread(new ThreadStart(TimerThread));
      thread.Start();
   }
   void TimerThread()
   {
      Thread.Sleep(m_period);
      OnTimer();
   }
   void OnTimer()
   {
      Console.WriteLine("OnTimer");
   }
}

這個(gè)代碼一般用于Wn32應(yīng)用中。每個(gè)定時(shí)器創(chuàng)建獨(dú)立的線程,并且等待指定的時(shí)間,然后呼叫回調(diào)函數(shù)。猶如你看到的那樣,這個(gè)實(shí)現(xiàn)的成本會(huì)非常高。如果你的應(yīng)用程序使用了多個(gè)定時(shí)器,相對(duì)的線程數(shù)量也會(huì)隨著使用定時(shí)器的數(shù)量而增長(zhǎng)。

現(xiàn)在我們有.NET 提供的線程池,我們可以從池中改變請(qǐng)求的等待函數(shù),這樣就十分有效,而且會(huì)提升系統(tǒng)的性能。我們會(huì)遇到兩個(gè)問題:

n          假如線程池已滿(所有的線程都在運(yùn)行中),那么這個(gè)請(qǐng)求排到隊(duì)列中等待,而且定時(shí)器不在精確。

n          假如創(chuàng)建了多個(gè)定時(shí)器,線程池會(huì)因?yàn)榈却鼈儠r(shí)間片失效而非常忙。

為了避免這些問題,.NET框架的線程池提供了獨(dú)立于時(shí)間的請(qǐng)求。用了這個(gè)函數(shù),我們可以不用任何線程就可以擁有成千上萬個(gè)定時(shí)器,一旦時(shí)間片失效,這時(shí),線程池將會(huì)處理這些請(qǐng)求。

這些特色出現(xiàn)在兩個(gè)不同的類中:

         System.Threading.Timer

                   定時(shí)器的簡(jiǎn)單版本,它運(yùn)行開發(fā)人員向線程池中的定期執(zhí)行的程序指定一個(gè)代理(delegate.

System.Timers.Timer

System.Threading.Timer的組件版本,允許開發(fā)人員把它拖放到一個(gè)窗口表單(form)中,可以把一個(gè)事件作為執(zhí)行的函數(shù)。

這非常有助于理解上述兩個(gè)類與另外一個(gè)稱為System.Windows.Forms.Timer.的類。這個(gè)類只是封裝了Win32中消息機(jī)制的計(jì)數(shù)器,如果你不準(zhǔn)備開發(fā)多線程應(yīng)用,那么就可以用這個(gè)類。

在下面的例子中,我們將用System.Threading.Timer 類,定時(shí)器的最簡(jiǎn)單實(shí)現(xiàn),我們只需要如下定義的構(gòu)造方法

public Timer(TimerCallback callback,
   object state,
   int dueTime,
   int period);

對(duì)于第一個(gè)參數(shù)(callback),我們可以指定定時(shí)執(zhí)行的函數(shù);第二個(gè)參數(shù)是傳遞給函數(shù)的通用對(duì)象;第三個(gè)參數(shù)是計(jì)時(shí)器開始執(zhí)行前的延時(shí);最后一個(gè)參數(shù)period,是兩個(gè)執(zhí)行之間的毫秒數(shù)。

下面的例子創(chuàng)建了兩個(gè)定時(shí)器,timer1timer2

class MainApp
{
   static void Main()
   {
      Timer timer1 = new Timer(new TimerCallback(OnTimer), 1, 0, 2000);
      Timer timer2 = new Timer(new TimerCallback(OnTimer), 2, 0, 3000);
      Console.ReadLine();
   }
   static void OnTimer(object obj)
   {
      Console.WriteLine("Timer: {0} Thread: {1} Is pool thread: {2}", 
         (int)obj,
         Thread.CurrentThread.GetHashCode(),
         Thread.CurrentThread.IsThreadPoolThread);
   }
}

輸出:

Timer: 1 Thread: 2 Is pool thread: True
Timer: 2 Thread: 2 Is pool thread: True
Timer: 1 Thread: 2 Is pool thread: True
Timer: 2 Thread: 2 Is pool thread: True
Timer: 1 Thread: 2 Is pool thread: True
Timer: 1 Thread: 2 Is pool thread: True
Timer: 2 Thread: 2 Is pool thread: True

猶如你看到的那樣,兩個(gè)定時(shí)器中的所有函數(shù)調(diào)用都在同一個(gè)線程中執(zhí)行(ID = 2),應(yīng)用程序使用的資源最小化了。

 

同步對(duì)象的執(zhí)行

相對(duì)于定時(shí)器,.NET線程池允許在執(zhí)行函數(shù)上同步對(duì)象,為了在多線程環(huán)境中的各線程之間共享資源,我們需要用.NET同步對(duì)象。

如果我們沒有線程,或者線程必須阻塞直到事件收到信號(hào),就像我前面提到一樣,這會(huì)增加應(yīng)用程序中總的線程數(shù)量,結(jié)果導(dǎo)致系統(tǒng)需要更多的資源和CPU時(shí)間。

線程池允許我們把請(qǐng)求進(jìn)行排隊(duì),直到某個(gè)特殊的同步對(duì)象收到信號(hào)后執(zhí)行。如果這個(gè)信號(hào)沒有收到,請(qǐng)求函數(shù)將不需要任何線程,所以可以保證系統(tǒng)性能最優(yōu)化。ThreadPool類提供了下面的方法:

public static RegisteredWaitHandle RegisterWaitForSingleObject(
   WaitHandle waitObject,
   WaitOrTimerCallback callBack,
   object state,
   int millisecondsTimeOutInterval,
   bool executeOnlyOnce);

 

第一個(gè)參數(shù),waitObject 可以是任何繼承于WaitHandle的對(duì)象:

         Mutex

     ManualResetEvent

     AutoResetEvent

就像你看到的那樣,只有系統(tǒng)的同步對(duì)象才能用在這里,就是繼承自WaitHandle的對(duì)象。你不能用其它任何的同步機(jī)制,比如moniter 或者 read-write 鎖。剩余的參數(shù)允許我們指明當(dāng)一個(gè)對(duì)象收到信號(hào)后執(zhí)行的函數(shù)(callBack;一個(gè)傳遞給函數(shù)的狀態(tài)(state); 線程池等待對(duì)象的最大時(shí)間 (millisecondsTimeOutInterval) 和一個(gè)標(biāo)識(shí)表明對(duì)象收到信號(hào)時(shí)函數(shù)只能執(zhí)行一次, (executeOnlyOnce). 下面的代理聲明目的是用在函數(shù)的回調(diào):

delegate void WaitOrTimerCallback(
   object state,
   bool timedOut);
 
如果參數(shù) timeout 設(shè)置的最大時(shí)間已經(jīng)失效,但是沒有同步對(duì)象收到信號(hào)的花,這個(gè)函數(shù)就會(huì)被調(diào)用。
下面的例子用了一個(gè)手工事件和一個(gè)互斥量來通知線程池中的執(zhí)行函數(shù):
class MainApp
{
   static void Main(string[] args)
   {
      ManualResetEvent evt = new ManualResetEvent(false);
      Mutex mtx = new Mutex(true);
      ThreadPool.RegisterWaitForSingleObject(evt,
         new WaitOrTimerCallback(PoolFunc),
         null, Timeout.Infinite, true);
      ThreadPool.RegisterWaitForSingleObject(mtx,
         new WaitOrTimerCallback(PoolFunc),
         null, Timeout.Infinite, true);
      for(int i=1;i<=5;i++)
      {
         Console.Write("{0}...", i);
         Thread.Sleep(1000);
      }
      Console.WriteLine();
      evt.Set();
      mtx.ReleaseMutex();
      Console.ReadLine();
   }
   static void PoolFunc(object obj, bool TimedOut)
   {
      Console.WriteLine("Synchronization object signaled, Thread: {0} Is pool: {1}", 
         Thread.CurrentThread.GetHashCode(),
         Thread.CurrentThread.IsThreadPoolThread);
   }
}

結(jié)束顯示兩個(gè)函數(shù)都在線程池的同一線程中執(zhí)行:

1...2...3...4...5...
Synchronization object signaled, Thread: 6 Is pool: True
Synchronization object signaled, Thread: 6 Is pool: True

 

異步I/O操作

線程池常見的應(yīng)用場(chǎng)景就是I/O操作。多數(shù)應(yīng)用系統(tǒng)需要讀磁盤,數(shù)據(jù)發(fā)送到Sockets,因特網(wǎng)連接等等。所有的這些操作都有一些特征,直到他們執(zhí)行操作時(shí),才需要CPU時(shí)間。.NET 框架為所有這些可能執(zhí)行的異步操作提供了I/O類。當(dāng)這些操作執(zhí)行完后,線程池中特定的函數(shù)會(huì)執(zhí)行。尤其是在服務(wù)器應(yīng)用程序中執(zhí)行多線程異步操作,性能會(huì)更好。

在第一個(gè)例子中,我們將把一個(gè)文件異步寫到硬盤中??匆豢?/span>FileStream 的構(gòu)造方法是如何使用的:

public FileStream(
   string path,
   FileMode mode,
   FleAccess access,
   FleShare share,
   int bufferSize,
   bool useAsync);

最后一個(gè)參數(shù)非常有趣,我們應(yīng)該對(duì)異步執(zhí)行文件的操作設(shè)置useAsyncTrue。如果我們沒有這樣做,即使我們用了異步函數(shù),它們的操作仍然會(huì)被主叫線程阻塞。

下面的例子說明了用一旦FileStream BeginWrite方法寫文件操作結(jié)束,線程池中的一個(gè)回調(diào)函數(shù)將會(huì)被執(zhí)行。注意我們可以在任何時(shí)候訪問IAsyncResult接口,它可以用來了解當(dāng)前操作的狀態(tài)。我們可以用CompletedSynchronously 屬性指示一個(gè)異步操作是否完成,而當(dāng)一個(gè)操作結(jié)束時(shí),IsCompleted 屬性會(huì)設(shè)上一個(gè)值。IAsyncResult 提供了很多有趣的屬性,比如:AsyncWaitHandle ,一旦操作完成,一個(gè)異步對(duì)象將會(huì)被通知。

class MainApp
{
   static void Main()
   {
      const string fileName = "temp.dat";
      FileStream fs;
      byte[] data = new Byte[10000];
      IAsyncResult ar;
 
      fs = new FileStream(fileName, 
         FileMode.Create, 
         FileAccess.Write, 
         FileShare.None, 
         1, 
         true);
      ar = fs.BeginWrite(data, 0, 10000,
         new AsyncCallback(UserCallback), null);
      Console.WriteLine("Main thread:{0}",
         Thread.CurrentThread.GetHashCode());
      Console.WriteLine("Synchronous operation: {0}",
         ar.CompletedSynchronously);
      Console.ReadLine();
   }
   static void UserCallback(IAsyncResult ar)
   {
      Console.Write("Operation finished: {0} on thread ID:{1}, is pool: {2}", 
         ar.IsCompleted, 
         Thread.CurrentThread.GetHashCode(), 
         Thread.CurrentThread.IsThreadPoolThread);
   }
}

輸出的結(jié)果顯示了操作是異步執(zhí)行的,一旦操作結(jié)束后,用戶的函數(shù)就在線程池中執(zhí)行。

Main thread:9
Synchronous operation: False
Operation finished: True on thread ID:10, is pool: True

在應(yīng)用Sockets的場(chǎng)景中,由于I/O操作通常比磁盤操作慢,這時(shí)用線程池就顯得尤為重要。過程跟前面提到的差不多,Socket 類提供了多個(gè)方法用于執(zhí)行異步操作:

         BeginRecieve

         BeginSend

         BeginConnect

         BeginAccept

假如你的服務(wù)器應(yīng)用使用了Socket來與客戶端通訊,一定會(huì)用到這些方法。這種方法取代了對(duì)每個(gè)客戶端連接都啟用一個(gè)線程的做法,所有的操作都在線程池中異步執(zhí)行。

         下面的例子用另外一個(gè)支持異步操作的類,HttpWebRequest。用這個(gè)類,我們可以建立一個(gè)到Web服務(wù)器的連接。這個(gè)方法叫BeginGetResponse, 但在這個(gè)例子中有一個(gè)很重要的區(qū)別。在上面最后一個(gè)示例中,我們沒有用到從操作中返回的結(jié)果。但是,我們現(xiàn)在需要當(dāng)一個(gè)操作結(jié)束時(shí)從Web服務(wù)器返回的響應(yīng),為了接收到這個(gè)信息,.NET中所有提供異步操作的類都提供了成對(duì)的方法。在HttpWebRequest這個(gè)類中,這個(gè)成對(duì)的方法就是:BeginGetResponse EndGetResponse。用了End版本,我們可以接收操作的結(jié)果。在我們的示例中,EndGetResponse 會(huì)從Web服務(wù)器接收響應(yīng)。

雖然可以在任何時(shí)間調(diào)用EndGetResponse 方法,但在我們的例子中是在回調(diào)函數(shù)中做的。僅僅是因?yàn)槲覀兿胫酪呀?jīng)做了異步請(qǐng)求。如果我們?cè)谥罢{(diào)用EndGetResponse ,這個(gè)調(diào)用將一直阻塞到操作完成。

         在下面的例子中,我們發(fā)送一個(gè)請(qǐng)求到Microsoft Web,然后顯示了接收到響應(yīng)的大小。

class MainApp
{
   static void Main()
   {
      HttpWebRequest request;
      IAsyncResult ar;
 
      request = (HttpWebRequest)WebRequest.CreateDefault(
         new Uri("http://www.microsoft.com"));
      ar = request.BeginGetResponse(new AsyncCallback(PoolFunc), request);
      Console.WriteLine("Synchronous: {0}", ar.CompletedSynchronously);
      Console.ReadLine();
   }
   static void PoolFunc(IAsyncResult ar)
   {
      HttpWebRequest request;
      HttpWebResponse response;
 
      Console.WriteLine("Response received on pool: {0}",
         Thread.CurrentThread.IsThreadPoolThread);
      request = (HttpWebRequest)ar.AsyncState;
      response = (HttpWebResponse)request.EndGetResponse(ar);
      Console.WriteLine("  Response size: {0}",
         response.ContentLength);
   }
}

下面剛開始結(jié)果信息表明,異步操作正在執(zhí)行:

Synchronous: False

過了一會(huì)兒,響應(yīng)接收到了。下面的結(jié)果顯示:

Response received on pool: True
   Response size: 27331

就像你看到的那樣,一旦收到響應(yīng),線程池的異步函數(shù)就會(huì)執(zhí)行。

 

監(jiān)視線程池

ThreadPool 類提供了兩個(gè)方法用來查詢線程池的狀態(tài)。第一個(gè)是我們可以從線程池獲取當(dāng)前可用的線程數(shù)量:
public static void GetAvailableThreads(
   out int workerThreads,
   out int completionPortThreads);

從方法中你可以看到兩種不同的線程:

         WorkerThreads

       工作線程是標(biāo)準(zhǔn)系統(tǒng)池的一部分。它們是被.NET框架托管的標(biāo)準(zhǔn)線程,多數(shù)函數(shù)是在這里執(zhí)行的。顯式的用戶請(qǐng)求(QueueUserWorkItem方法),基于異步對(duì)象的方法(RegisterWaitForSingleObject)和定時(shí)器(Timer類)

 

CompletionPortThreads

這種線程常常用來I/O操作,Windows NT, Windows 2000 Windows XP提供了一個(gè)步執(zhí)行的對(duì)象,叫做IOCompletionPortAPI和異步對(duì)象關(guān)聯(lián)起來,用少量的資源和有效的方法,我們就可以調(diào)用系統(tǒng)線程池的異步I/O操作。但是在Windows 95, Windows 98, Windows Me有一些局限。比如: 在某些設(shè)備上,沒有提供IOCompletionPorts 功能和一些異步操作,如磁盤和郵件槽。在這里你可以看到.NET框架的最大特色:一次編譯,可以在多個(gè)系統(tǒng)下運(yùn)行。根據(jù)不同的目標(biāo)平臺(tái),.NET 框架會(huì)決定是否使用IOCompletionPorts API,用最少的資源達(dá)到最好的性能。

這節(jié)包含一個(gè)使用Socket 類的例子。在這個(gè)示例中,我們將異步建立一個(gè)連接到本地的Web服務(wù)器,然后發(fā)送一個(gè)Get請(qǐng)求。通過這個(gè)例子,我們可以很容易地鑒別這兩種不同的線程。

using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Text;
 
namespace ThreadPoolTest
{
   class MainApp
   {
      static void Main()
      {
         Socket s;
         IPHostEntry hostEntry;
         IPAddress ipAddress;
         IPEndPoint ipEndPoint;
         
         hostEntry = Dns.Resolve(Dns.GetHostName());
         ipAddress = hostEntry.AddressList[0];
         ipEndPoint = new IPEndPoint(ipAddress, 80);
         s = new Socket(ipAddress.AddressFamily,
            SocketType.Stream, ProtocolType.Tcp);
         s.BeginConnect(ipEndPoint, new AsyncCallback(ConnectCallback),s);
         
         Console.ReadLine();
      }
      static void ConnectCallback(IAsyncResult ar)
      {
         byte[] data;
         Socket s = (Socket)ar.AsyncState;
         data = Encoding.ASCII.GetBytes("GET /\n");
 
         Console.WriteLine("Connected to localhost:80");
         ShowAvailableThreads();
         s.BeginSend(data, 0,data.Length,SocketFlags.None,
            new AsyncCallback(SendCallback), null);
      }
      static void SendCallback(IAsyncResult ar)
      {
         Console.WriteLine("Request sent to localhost:80");
         ShowAvailableThreads();
      }
      static void ShowAvailableThreads()
      {
         int workerThreads, completionPortThreads;
 
         ThreadPool.GetAvailableThreads(out workerThreads,
            out completionPortThreads);
         Console.WriteLine("WorkerThreads: {0}," + 
            " CompletionPortThreads: {1}",
            workerThreads, completionPortThreads);
      }
   }
}

 

如果你在Microsoft Windows NT, Windows 2000, or Windows XP 下運(yùn)行這個(gè)程序,你將會(huì)看到如下結(jié)果:

Connected to localhost:80
WorkerThreads: 24, CompletionPortThreads: 25
Request sent to localhost:80
WorkerThreads: 25, CompletionPortThreads: 24

 

如你所看到地那樣,連接用了工作線程,而發(fā)送數(shù)據(jù)用了一個(gè)完成端口(CompletionPort),接著看下面的順序:

1.   我們得到一個(gè)本地IP地址,然后異步連接到那里。

2.   Socket在工作線程上執(zhí)行異步連接操作,因?yàn)樵?/span>Socket上,不能用Windows IOCompletionPorts來建立連接。

3.   一旦連接建立了,Socket類調(diào)用指明的函數(shù)ConnectCallback,這個(gè)回調(diào)函數(shù)顯示了線程池中可用的線程數(shù)量。我們可以看到這些是在工作線程中執(zhí)行的。

4.   在用ASCII碼對(duì)Get請(qǐng)求進(jìn)行編碼后,我們用BeginSend方法從同樣的函數(shù)ConnectCallback 中發(fā)送一個(gè)異步請(qǐng)求。

5.   Socket上的發(fā)送和接收操作可以通過IOCompletionPort 來執(zhí)行異步操作,所以當(dāng)請(qǐng)求做完后,回調(diào)函數(shù)就會(huì)在一個(gè)CompletionPort類型的線程中執(zhí)行。因?yàn)楹瘮?shù)本身顯示了可用的線程數(shù)量,所以我們可以通過這個(gè)來查看,對(duì)應(yīng)的完成端口數(shù)已經(jīng)減少了多少。

如果我們?cè)?/span>Windows 95, Windows 98, 或者 Windows Me平臺(tái)上運(yùn)行相同的代碼,會(huì)出現(xiàn)相同的連接結(jié)果,請(qǐng)求將被發(fā)送到工作線程,而非完成端口。你應(yīng)該知道的很重要的一點(diǎn)就是,Socket類總是會(huì)利用最優(yōu)的可用機(jī)制,所以你在開發(fā)應(yīng)用時(shí),可以不用考慮目標(biāo)平臺(tái)是什么。

       你已經(jīng)看到在上面的例子中每種類型的線程可用的最大數(shù)是25。我們可以用GetMaxThreads返回這個(gè)值:

public static void GetMaxThreads(
   out int workerThreads,
   out int completionPortThreads);

一旦到了最大的數(shù)量,就不會(huì)創(chuàng)建新線程,所有的請(qǐng)求都將被排隊(duì)。假如你看過ThreadPool類的所有方法,你將發(fā)現(xiàn)沒有一個(gè)允許我們更改最大數(shù)的方法。就像我們前面提到的那樣,線程池是每個(gè)處理過程的唯一共享資源。這就是為什么不可能讓應(yīng)用程序域去更改這個(gè)配置的原因。想象一下出現(xiàn)這種情況的后果,如果有第三方組件把線程池中線程的最大數(shù)改為1,整個(gè)應(yīng)用都會(huì)停止工作,甚至在進(jìn)程中其它的應(yīng)用程序域都將受到影響。同樣的原因,公共語言運(yùn)行時(shí)的宿主也有可能去更改這個(gè)配置。比如:ASP.NET允許系統(tǒng)管理員更改這個(gè)數(shù)字。

 

死鎖

在你的應(yīng)用程序使用線程池之前,還有一個(gè)東西你應(yīng)該知道:死鎖。在線程池中執(zhí)行一個(gè)實(shí)現(xiàn)不好的異步對(duì)象可能導(dǎo)致你的整個(gè)應(yīng)用系統(tǒng)中止運(yùn)行。

       設(shè)想你的代碼中有個(gè)方法,它需要通過Socket連接到一個(gè)Web服務(wù)器上。一個(gè)可能的實(shí)現(xiàn)就是用Socket 類中的BeginConnect方法異步打開一個(gè)連接,然后用EndConnect方法等待連接的建立。代碼如下:

        class ConnectionSocket
{
   public void Connect()
   {
      IPHostEntry ipHostEntry = Dns.Resolve(Dns.GetHostName());
      IPEndPoint ipEndPoint = new IPEndPoint(ipHostEntry.AddressList[0],
         80);
      Socket s = new Socket(ipEndPoint.AddressFamily, SocketType.Stream,
         ProtocolType.Tcp);
      IAsyncResult ar = s.BeginConnect(ipEndPoint, null, null);
      s.EndConnect(ar);
   }
}

多快,多好。調(diào)用BeginConnect使異步操作在線程池中執(zhí)行,而EndConnect一直阻塞到連接被建立。

       如果線程池中的一個(gè)執(zhí)行函數(shù)中用了這個(gè)類的方法,將會(huì)發(fā)生什么事情呢?設(shè)想線程池的大小只有兩個(gè)線程,然后用我們的連接類創(chuàng)建了兩個(gè)異步對(duì)象。當(dāng)這兩個(gè)函數(shù)同時(shí)在池中執(zhí)行時(shí),線程池已經(jīng)沒有用于其它請(qǐng)求的空間了,除非直到某個(gè)函數(shù)結(jié)束。問題是這些函數(shù)調(diào)用了我們類中的Connect方法,這個(gè)方法在線程池中又發(fā)起了一個(gè)異步操作。但線程池一直是滿的,所以請(qǐng)求就一直等待任何空閑線程的出現(xiàn)。不幸的是,這將永遠(yuǎn)不會(huì)發(fā)生,因?yàn)槭褂镁€程池的函數(shù)正等待隊(duì)列函數(shù)的結(jié)束。結(jié)論就是:我們的應(yīng)用系統(tǒng)已經(jīng)阻塞了。

       我們以此推斷25個(gè)線程的線程池的行為。假如25個(gè)函數(shù)都等待異步對(duì)象操作的結(jié)束。結(jié)果將是一樣的,死鎖一樣會(huì)出現(xiàn)。

       在下面的代碼片斷中,我們使用了這個(gè)類來說明問題:

class MainApp
{
   static void Main()
   {
      for(int i=0;i<30;i++)
      {
         ThreadPool.QueueUserWorkItem(new WaitCallback(PoolFunc));
      }
      Console.ReadLine();
   }
 
   static void PoolFunc(object state)
   {
      int workerThreads,completionPortThreads;
      ThreadPool.GetAvailableThreads(out workerThreads,
         out completionPortThreads);
      Console.WriteLine("WorkerThreads: {0}, CompletionPortThreads: {1}", 
         workerThreads, completionPortThreads);
 
      Thread.Sleep(15000);
      ConnectionSocket connection = new ConnectionSocket();
      connection.Connect();
   }
}

如果你運(yùn)行這個(gè)例子,你將看到池中的線程是如何把線程的可用數(shù)量減少到零的,接著應(yīng)用中止,死鎖出現(xiàn)了。

       如果你想在你的應(yīng)用中避免出現(xiàn)死鎖,永遠(yuǎn)不要阻塞正在等待線程池中的其它函數(shù)的線程。這看起來很容易,但記住這個(gè)規(guī)則意味著有兩條:

n          不要?jiǎng)?chuàng)建這樣的類,它的同步方法在等待異步函數(shù)。因?yàn)檫@種類可能被線程池中的線程調(diào)用。

n          不要在任何異步函數(shù)中使用這樣的類,如果它正等待著這個(gè)異步函數(shù)。

如果你想檢測(cè)到應(yīng)用中的死鎖情況,那么就當(dāng)你的系統(tǒng)掛起時(shí),檢查線程池中的線程可用數(shù)。線程的可用數(shù)量已經(jīng)沒有并且CPU的使用率為0 ,這是很明顯的死鎖癥狀。你應(yīng)該檢查你的代碼,以確定哪個(gè)在線程中執(zhí)行的函數(shù)正在等待異步操作,然后刪除它。

      

有關(guān)安全性

         如果你再看看ThreadPool類,你會(huì)看到有兩個(gè)方法我們沒有用到,UnsafeQueueUserWorkItem UnsafeRegisterWaitForSingleObject。 為了完全理解這些方法,首先,我們必須回憶 .NET框架中安全策略是怎么運(yùn)作的。

          Windows安全機(jī)制是關(guān)注資源。操作系統(tǒng)本身允許對(duì)文件,用戶,注冊(cè)表鍵值和任何其它的系統(tǒng)資源設(shè)定權(quán)限。這種方法對(duì)應(yīng)用系統(tǒng)的用戶認(rèn)證非常有效,但當(dāng)出現(xiàn)用戶對(duì)他使用的系統(tǒng)產(chǎn)生不信任的情況時(shí),這就會(huì)有些局限性。例如這些程序是從Internet下載的。在這種情況下,一旦用戶安裝了這個(gè)程序,它就可以執(zhí)行用戶權(quán)限范圍內(nèi)的任何操作。舉個(gè)例子,假如用戶可以刪除他公司內(nèi)的任何共享文件,任何從Internet下載的程序也都可以這樣做。

         .NET 提供了應(yīng)用到程序的安全性策略,而不是用戶。這就是說,在用戶權(quán)限的范圍內(nèi),我們可以限制任何執(zhí)行單元(程序集)使用的資源。通過MMC,我們可以根據(jù)條件定義一組程序集,然后為每組設(shè)置不同的策略,一個(gè)典型的例子就是限制從Internet下載的程序訪問磁盤的權(quán)限。

         為了讓這個(gè)功能運(yùn)轉(zhuǎn)起來,.NET 框架必須維護(hù)一個(gè)不同程序集之間的調(diào)用棧。假設(shè)一個(gè)應(yīng)用沒有權(quán)限訪問磁盤,但是它調(diào)用了一個(gè)對(duì)整個(gè)系統(tǒng)都可以訪問的類庫,當(dāng)?shù)诙€(gè)程序集執(zhí)行一個(gè)磁盤的操作時(shí),設(shè)置到這個(gè)程序集的權(quán)限允許這樣做,但是權(quán)限不會(huì)被應(yīng)用到主叫程序集,.NET不僅要檢查當(dāng)前程序集的權(quán)限,而且會(huì)檢查整個(gè)調(diào)用棧的權(quán)限。這個(gè)棧已經(jīng)被高度優(yōu)化了,但是它們給兩個(gè)不同程序集之間的調(diào)用增加了額外的負(fù)擔(dān)。

         UnsafeQueueUserWorkItem , UnsafeRegisterWaitForSingleObject QueueUserWorkItem , RegisterWaitForSingleObject兩個(gè)方法類似。由于是非安全版本不會(huì)維護(hù)它們執(zhí)行函數(shù)之間的調(diào)用棧,所以非安全版本運(yùn)行的更快些。但是回調(diào)函數(shù)將只在當(dāng)前程序集的安全策略下執(zhí)行,它就不能應(yīng)用權(quán)限到整個(gè)調(diào)用棧中的程序集。

         我的建議是僅在性能非常重要的、安全已經(jīng)控制好的極端情況下才用非安全版本。例如,你構(gòu)建的應(yīng)用程序不會(huì)被其它的程序集調(diào)用,或者僅被很明確清楚的程序集使用,那么你可以用非安全版本。如果你開發(fā)的類庫會(huì)被第三方應(yīng)用程序中使用,那么你就不應(yīng)該用這些方法,因?yàn)樗鼈兛赡苡媚愕膸飓@取訪問系統(tǒng)資源的權(quán)限。

         在下面例子中,你可以看到用UnsafeQueueUserWorkItem方法的風(fēng)險(xiǎn)。我們將構(gòu)建兩個(gè)單獨(dú)的程序集,在第一個(gè)程序集中我們將在線程池中創(chuàng)建一個(gè)文件,然后我們將導(dǎo)出一個(gè)類以使這個(gè)操作可以被其它的程序集執(zhí)行。

using System;
using System.Threading;
using System.IO;
namespace ThreadSecurityTest
{
   public class PoolCheck
   {
      public void CheckIt()
      {
         ThreadPool.QueueUserWorkItem(new WaitCallback(UserItem), null);
      }
      private void UserItem(object obj)
      {
         FileStream fs = new FileStream("test.dat", FileMode.Create);
         fs.Close();
         Console.WriteLine("File created");
      }
   }
}

第二個(gè)程序集引用了第一個(gè),并且用了CheckIt 方法去創(chuàng)建一個(gè)文件:

using System;
namespace ThreadSecurityTest
{
   class MainApp
   {
      static void Main()
      {
         PoolCheck pc = new PoolCheck();
         pc.CheckIt();
         Console.ReadLine();
      }
   }
}

編譯這兩個(gè)程序集,然后運(yùn)行main應(yīng)用。默認(rèn)情況下,你的應(yīng)用被配置為允許執(zhí)行磁盤操作,所以系統(tǒng)成功生成文件。

     File created

現(xiàn)在,打開.NET框架的配置。為了簡(jiǎn)化這個(gè)例子,我們僅創(chuàng)建一個(gè)代碼組關(guān)聯(lián)到main應(yīng)用。接著展開 運(yùn)行庫安全策略/ 計(jì)算機(jī)/ 代碼組/ All_Code /,增加一個(gè)叫ThreadSecurityTest的組。在向?qū)е校x擇Hash 條件并導(dǎo)入Hash到我們的應(yīng)用中,設(shè)置為Internet 級(jí)別,并選擇“該策略級(jí)別將只具有與此代碼組關(guān)聯(lián)的權(quán)限集中的權(quán)限”選項(xiàng)。

運(yùn)行應(yīng)用程序,看看會(huì)發(fā)生什么情況:

Unhandled Exception: System.Security.SecurityException: Request for the 
   permission of type System.Security.Permissions.FileIOPermission, 
      mscorlib, Version=1.0.3300.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089 failed.

我們的策略開始工作,系統(tǒng)已經(jīng)不能創(chuàng)建文件了。這是因?yàn)?/span>.NET框架為我們維護(hù)了一個(gè)調(diào)用棧才使它成為了可能,雖然創(chuàng)建文件的庫有權(quán)限去訪問系統(tǒng)。

現(xiàn)在把庫中的QueueUserWorkItem替換為UnsafeQueueUserWorkItem再次編譯程序集,然后運(yùn)行Main程序?,F(xiàn)在的結(jié)果是:

File created

即使我們的系統(tǒng)沒有足夠的權(quán)限去訪問磁盤,但我們已經(jīng)創(chuàng)建了一個(gè)向整個(gè)系統(tǒng)公開它的功能的庫,卻沒有維護(hù)它的調(diào)用棧。記住一個(gè)金牌規(guī)則: 僅在你的代碼不允許讓其它的應(yīng)用系統(tǒng)調(diào)用,或者當(dāng)你想要嚴(yán)格限制訪問很明確清楚的程序集,才使用非安全的函數(shù)。

 

結(jié)束

         在這篇文章中,我們知道了為什么在我們的服務(wù)器應(yīng)用中需要使用線程池來優(yōu)化資源和CPU的利用。我們學(xué)習(xí)了一個(gè)線程池是如何實(shí)現(xiàn)的,需要考慮多個(gè)因素如:CPU使用的百分比,隊(duì)列請(qǐng)求或者系統(tǒng)的處理器數(shù)量。

         .NET提供了豐富的線程池的功能以讓我們的應(yīng)用程序使用, 并且與.NET框架的類緊密地集成在一起。這個(gè)線程池是高度優(yōu)化了的,它只需要最少的CPU時(shí)間和資源,而且總能適應(yīng)目標(biāo)平臺(tái)。

         因?yàn)榕c框架集成在一起,所以框架中的大部分類都提供了使用線程池的內(nèi)在功能,給開發(fā)人員提供了集中管理和監(jiān)視應(yīng)用中的線程池的功能。鼓勵(lì)第三方組件使用線程池,這樣它們的客戶就可以享受.NET所提供的全部功能。允許執(zhí)行用戶函數(shù),定時(shí)器,I/O操作和同步對(duì)象。

         假如你在開發(fā)服務(wù)器應(yīng)用系統(tǒng),只要有可能就在你的請(qǐng)求處理系統(tǒng)中使用線程池?;蛘吣汩_發(fā)了一個(gè)讓服務(wù)器程序使用的庫,那么盡可能提供系統(tǒng)線程池的異步對(duì)象處理。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
多線程的應(yīng)用
C#中的線程(三) 使用多線程
C#3.0學(xué)習(xí)筆記(12)進(jìn)程,線程和異步編程
petshop4.0學(xué)習(xí)筆記之消息隊(duì)列(MSMQ)
.NET基礎(chǔ)拾遺(5)多線程開發(fā)基礎(chǔ)(上)
C#多線程簡(jiǎn)單例子
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服