激情五月天婷婷,亚洲愉拍一区二区三区,日韩视频一区,a√天堂中文官网8

<ul id="buwfs"><strike id="buwfs"><strong id="buwfs"></strong></strike></ul>
    <output id="buwfs"></output>
  • <dfn id="buwfs"><source id="buwfs"></source></dfn>
      <dfn id="buwfs"><td id="buwfs"></td></dfn>
      <div id="buwfs"><small id="buwfs"></small></div>
      <dfn id="buwfs"><source id="buwfs"></source></dfn>
      1. <dfn id="buwfs"><td id="buwfs"></td></dfn>
        始創(chuàng)于2000年 股票代碼:831685
        咨詢熱線:0371-60135900 注冊(cè)有禮 登錄
        • 掛牌上市企業(yè)
        • 60秒人工響應(yīng)
        • 99.99%連通率
        • 7*24h人工
        • 故障100倍補(bǔ)償
        全部產(chǎn)品
        您的位置: 網(wǎng)站首頁(yè) > 幫助中心>文章內(nèi)容

        細(xì)說(shuō)多線程(四)—— CLR線程池的工作者線程

        發(fā)布時(shí)間:  2012/9/16 6:35:10

        目錄

        一、線程的定義

        二、線程的基礎(chǔ)知識(shí)

        三、以ThreadStart方式實(shí)現(xiàn)多線程

        四、CLR線程池的工作者線程

        五、CLR線程池的I/O線程

        六、異步 SqlCommand

        七、并行編程與PLINQ

        八、計(jì)時(shí)器與鎖

         

        四、CLR線程池的工作者線程

        4.1 關(guān)于CLR線程池

        使用ThreadStart與ParameterizedThreadStart建立新線程非常簡(jiǎn)單,但通過(guò)此方法建立的線程難于管理,若建立過(guò)多的線程反而會(huì)影響系統(tǒng)的性能。
        有 見(jiàn)及此,.NET引入CLR線程池這個(gè)概念。CLR線程池并不會(huì)在CLR初始化的時(shí)候立刻建立線程,而是在應(yīng)用程序要?jiǎng)?chuàng)建線程來(lái)執(zhí)行任務(wù)時(shí),線程池才初始 化一個(gè)線程。線程的初始化與其他的線程一樣。在完成任務(wù)以后,該線程不會(huì)自行銷(xiāo)毀,而是以掛起的狀態(tài)返回到線程池。直到應(yīng)用程序再次向線程池發(fā)出請(qǐng)求時(shí), 線程池里掛起的線程就會(huì)再度激活執(zhí)行任務(wù)。這樣既節(jié)省了建立線程所造成的性能損耗,也可以讓多個(gè)任務(wù)反復(fù)重用同一線程,從而在應(yīng)用程序生存期內(nèi)節(jié)約大量開(kāi) 銷(xiāo)。

        注意:通過(guò)CLR線程池所建立的線程總是默認(rèn)為后臺(tái)線程,優(yōu)先級(jí)數(shù)為T(mén)hreadPriority.Normal。

         

        4.2 工作者線程與I/O線程

        CLR線程池分為工作者線程(workerThreads)與I/O線程 (completionPortThreads) 兩種,工作者線程是主要用作管理CLR內(nèi)部對(duì)象的運(yùn)作,I/O(Input/Output) 線程顧名思義是用于與外部系統(tǒng)交換信息,IO線程的細(xì)節(jié)將在下一節(jié)詳細(xì)說(shuō)明。

        通 過(guò)ThreadPool.GetMax(out int workerThreads,out int completionPortThreads )和 ThreadPool.SetMax( int workerThreads, int completionPortThreads)兩個(gè)方法可以分別讀取和設(shè)置CLR線程池中工作者線程與I/O線程的最大線程數(shù)。在 Framework2.0中最大線程默認(rèn)為25*CPU數(shù),在Framewok3.0、4.0中最大線程數(shù)默認(rèn)為250*CPU數(shù),在近年 I3,I5,I7 CPU出現(xiàn)后,線程池的最大值一般默認(rèn)為1000、2000。
        若想測(cè)試線程池中有多少的線程正在投入使用,可以通過(guò)ThreadPool.GetAvailableThreads( out int workerThreads,out int completionPortThreads ) 方法。

        使用CLR線程池的工作者線程一般有兩種方式,一是直接通過(guò) ThreadPool.QueueUserWorkItem() 方法,二是通過(guò)委托,下面將逐一細(xì)說(shuō)。

         

        4.3 通過(guò)QueueUserWorkItem啟動(dòng)工作者線程

        ThreadPool線程池中包含有兩個(gè)靜態(tài)方法可以直接啟動(dòng)工作者線程:
        一為 ThreadPool.QueueUserWorkItem(WaitCallback)
        二為 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 

        先把WaitCallback委托指向一個(gè)帶有Object參數(shù)的無(wú)返回值方法,再使用 ThreadPool.QueueUserWorkItem(WaitCallback) 就可以異步啟動(dòng)此方法,此時(shí)異步方法的參數(shù)被視為null 。

         1     class Program
         2     {
         3         static void Main(string[] args)
         4         {
         5             //把CLR線程池的最大值設(shè)置為1000
        6 ThreadPool.SetMaxThreads(1000, 1000); 7 //顯示主線程啟動(dòng)時(shí)線程池信息
        8 ThreadMessage("Start"); 9 //啟動(dòng)工作者線程
        10 ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback)); 11 Console.ReadKey(); 12 } 13 14 static void AsyncCallback(object state) 15 { 16 Thread.Sleep(200); 17 ThreadMessage("AsyncCallback"); 18 Console.WriteLine("Async thread do work!"); 19 } 20 21 //顯示線程現(xiàn)狀
        22 static void ThreadMessage(string data) 23 { 24 string message = string.Format("{0}\n CurrentThreadId is {1}", 25 data, Thread.CurrentThread.ManagedThreadId); 26 Console.WriteLine(message); 27 } 28 }

        運(yùn)行結(jié)果

         

        使用 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 方法可以把object對(duì)象作為參數(shù)傳送到回調(diào)函數(shù)中。
        下面例子中就是把一個(gè)string對(duì)象作為參數(shù)發(fā)送到回調(diào)函數(shù)當(dāng)中。

         1     class Program
         2     {
         3         static void Main(string[] args)
         4         {
         5             //把線程池的最大值設(shè)置為1000
        6 ThreadPool.SetMaxThreads(1000, 1000); 7 8 ThreadMessage("Start"); 9 ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback),"Hello Elva"); 10 Console.ReadKey(); 11 } 12 13 static void AsyncCallback(object state) 14 { 15 Thread.Sleep(200); 16 ThreadMessage("AsyncCallback"); 17 18 string data = (string)state; 19 Console.WriteLine("Async thread do work!\n"+data); 20 } 21 22 //顯示線程現(xiàn)狀
        23 static void ThreadMessage(string data) 24 { 25 string message = string.Format("{0}\n CurrentThreadId is {1}", 26 data, Thread.CurrentThread.ManagedThreadId); 27 Console.WriteLine(message); 28 } 29 }

        運(yùn)行結(jié)果

         

        通 過(guò)ThreadPool.QueueUserWorkItem啟動(dòng)工作者線程雖然是方便,但WaitCallback委托指向的必須是一個(gè)帶有 Object參數(shù)的無(wú)返回值方法,這無(wú)疑是一種限制。若方法需要有返回值,或者帶有多個(gè)參數(shù),這將多費(fèi)周折。有見(jiàn)及此,.NET提供了另一種方式去建立工 作者線程,那就是委托。

         

        4.4  委托類(lèi)       

        使用CLR線程池中的工作者線程,最靈活最常用的方式就是使用委托的異步方法,在此先簡(jiǎn)單介紹一下委托類(lèi)。

        當(dāng)定義委托后,.NET就會(huì)自動(dòng)創(chuàng)建一個(gè)代表該委托的類(lèi),下面可以用反射方式顯示委托類(lèi)的方法成員(對(duì)反射有興趣的朋友可以先參考一下“.NET基礎(chǔ)篇——反射的奧妙”)

         1     class Program
         2     {
         3         delegate void MyDelegate();
         4 
         5         static void Main(string[] args)
         6         {
         7             MyDelegate delegate1 = new MyDelegate(AsyncThread);
         8             //顯示委托類(lèi)的幾個(gè)方法成員     
        9 var methods=delegate1.GetType().GetMethods(); 10 if (methods != null) 11 foreach (MethodInfo info in methods) 12 Console.WriteLine(info.Name); 13 Console.ReadKey(); 14 } 15 }

        委托類(lèi)包括以下幾個(gè)重要方法

        1     public class MyDelegate:MulticastDelegate
        2     {
        3         public MyDelegate(object target, int methodPtr);
        4         //調(diào)用委托方法
        5 public virtual void Invoke(); 6 //異步委托
        7 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state); 8 public virtual void EndInvoke(IAsyncResult result); 9 }

        當(dāng)調(diào)用Invoke()方法時(shí),對(duì)應(yīng)此委托的所有方法都會(huì)被執(zhí)行。而B(niǎo)eginInvoke與EndInvoke則支持委托方法的異步調(diào)用,由BeginInvoke啟動(dòng)的線程都屬于CLR線程池中的工作者線程,在下面將詳細(xì)說(shuō)明。

         

        4.5  利用BeginInvoke與EndInvoke完成異步委托方法

        首 先建立一個(gè)委托對(duì)象,通過(guò)IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state) 異步調(diào)用委托方法,BeginInvoke 方法除最后的兩個(gè)參數(shù)外,其它參數(shù)都是與方法參數(shù)相對(duì)應(yīng)的。通過(guò) BeginInvoke 方法將返回一個(gè)實(shí)現(xiàn)了 System.IAsyncResult 接口的對(duì)象,之后就可以利用EndInvoke(IAsyncResult ) 方法就可以結(jié)束異步操作,獲取委托的運(yùn)行結(jié)果。

         1     class Program
         2     {
         3         delegate string MyDelegate(string name);
         4 
         5         static void Main(string[] args)
         6         {
         7             ThreadMessage("Main Thread");
         8             
         9             //建立委托
        10 MyDelegate myDelegate = new MyDelegate(Hello); 11 //異步調(diào)用委托,獲取計(jì)算結(jié)果
        12 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null); 13 //完成主線程其他工作
        14 .............
        15 //等待異步方法完成,調(diào)用EndInvoke(IAsyncResult)獲取運(yùn)行結(jié)果
        16 string data=myDelegate.EndInvoke(result); 17 Console.WriteLine(data); 18 19 Console.ReadKey(); 20 } 21 22 static string Hello(string name) 23 { 24 ThreadMessage("Async Thread"); 25 Thread.Sleep(2000); //虛擬異步工作 26 return "Hello " + name; 27 } 28 29 //顯示當(dāng)前線程
        30 static void ThreadMessage(string data) 31 { 32 string message = string.Format("{0}\n ThreadId is:{1}", 33 data,Thread.CurrentThread.ManagedThreadId); 34 Console.WriteLine(message); 35 } 36 }

        運(yùn)行結(jié)果

         

        4.6  善用IAsyncResult

        在 以上例子中可以看見(jiàn),如果在使用myDelegate.BeginInvoke后立即調(diào)用myDelegate.EndInvoke,那在異步線程未完成 工作以前主線程將處于阻塞狀態(tài),等到異步線程結(jié)束獲取計(jì)算結(jié)果后,主線程才能繼續(xù)工作,這明顯無(wú)法展示出多線程的優(yōu)勢(shì)。此時(shí)可以好好利用 IAsyncResult 提高主線程的工作性能,IAsyncResult有以下成員:

        1 public interface IAsyncResult
        2 {
        3     object AsyncState {get;}            //獲取用戶定義的對(duì)象,它限定或包含關(guān)于異步操作的信息。
        4     WailHandle AsyncWaitHandle {get;}   //獲取用于等待異步操作完成的 WaitHandle。
        5     bool CompletedSynchronously {get;}  //獲取異步操作是否同步完成的指示。
        6     bool IsCompleted {get;}             //獲取異步操作是否已完成的指示。
        7 }

        通過(guò)輪詢方式,使用IsCompleted屬性判斷異步操作是否完成,這樣在異步操作未完成前就可以讓主線程執(zhí)行另外的工作。

         1     class Program
         2     {
         3         delegate string MyDelegate(string name);
         4 
         5         static void Main(string[] args)
         6         {
         7             ThreadMessage("Main Thread");
         8             
         9             //建立委托
        10 MyDelegate myDelegate = new MyDelegate(Hello); 11 //異步調(diào)用委托,獲取計(jì)算結(jié)果
        12 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null); 13 //在異步線程未完成前執(zhí)行其他工作
        14 while (!result.IsCompleted) 15 { 16 Thread.Sleep(200); //虛擬操作 17 Console.WriteLine("Main thead do work!"); 18 } 19 string data=myDelegate.EndInvoke(result); 20 Console.WriteLine(data); 21 22 Console.ReadKey(); 23 } 24 25 static string Hello(string name) 26 { 27 ThreadMessage("Async Thread"); 28 Thread.Sleep(2000); 29 return "Hello " + name; 30 } 31 32 static void ThreadMessage(string data) 33 { 34 string message = string.Format("{0}\n ThreadId is:{1}", 35 data,Thread.CurrentThread.ManagedThreadId); 36 Console.WriteLine(message); 37 } 38 }

        運(yùn)行結(jié)果:

         

        除 此以外,也可以使用WailHandle完成同樣的工作,WaitHandle里面包含有一個(gè)方法WaitOne(int timeout),它可以判斷委托是否完成工作,在工作未完成前主線程可以繼續(xù)其他工作。運(yùn)行下面代碼可得到與使用 IAsyncResult.IsCompleted 同樣的結(jié)果,而且更簡(jiǎn)單方便 。

         1 namespace Test
         2 {
         3     class Program
         4     {
         5         delegate string MyDelegate(string name);
         6 
         7         static void Main(string[] args)
         8         {
         9             ThreadMessage("Main Thread");
        10             
        11             //建立委托
        12 MyDelegate myDelegate = new MyDelegate(Hello); 13 14 //異步調(diào)用委托,獲取計(jì)算結(jié)果
        15 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null); 16 17 while (!result.AsyncWaitHandle.WaitOne(200)) 18 { 19 Console.WriteLine("Main thead do work!"); 20 } 21 string data=myDelegate.EndInvoke(result); 22 Console.WriteLine(data); 23 24 Console.ReadKey(); 25 } 26 27 static string Hello(string name) 28 { 29 ThreadMessage("Async Thread"); 30 Thread.Sleep(2000); 31 return "Hello " + name; 32 } 33 34 static void ThreadMessage(string data) 35 { 36 string message = string.Format("{0}\n ThreadId is:{1}", 37 data,Thread.CurrentThread.ManagedThreadId); 38 Console.WriteLine(message); 39 } 40 }

        當(dāng)要監(jiān)視多個(gè)運(yùn)行對(duì)象的時(shí)候,使用IAsyncResult.WaitHandle.WaitOne可就派不上用場(chǎng)了。
        幸好.NET為WaitHandle準(zhǔn)備了另外兩個(gè)靜態(tài)方法:WaitAny(waitHandle[], int)與WaitAll (waitHandle[] , int)。
        其中WaitAll在等待所有waitHandle完成后再返回一個(gè)bool值。
        而WaitAny是等待其中一個(gè)waitHandle完成后就返回一個(gè)int,這個(gè)int是代表已完成waitHandle在waitHandle[]中的數(shù)組索引。
        下面就是使用WaitAll的例子,運(yùn)行結(jié)果與使用 IAsyncResult.IsCompleted 相同。

         1     class Program
         2     {
         3         delegate string MyDelegate(string name);
         4 
         5         static void Main(string[] args)
         6         {
         7             ThreadMessage("Main Thread");
         8             
         9             //建立委托
        10 MyDelegate myDelegate = new MyDelegate(Hello); 11 12 //異步調(diào)用委托,獲取計(jì)算結(jié)果
        13 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null); 14 15 //此處可加入多個(gè)檢測(cè)對(duì)象
        16 WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle,........ }; 17 while (!WaitHandle.WaitAll(waitHandleList,200)) 18 { 19 Console.WriteLine("Main thead do work!"); 20 } 21 string data=myDelegate.EndInvoke(result); 22 Console.WriteLine(data); 23 24 Console.ReadKey(); 25 } 26 27 static string Hello(string name) 28 { 29 ThreadMessage("Async Thread"); 30 Thread.Sleep(2000); 31 return "Hello " + name; 32 } 33 34 static void ThreadMessage(string data) 35 { 36 string message = string.Format("{0}\n ThreadId is:{1}", 37 data,Thread.CurrentThread.ManagedThreadId); 38 Console.WriteLine(message); 39 } 40 }

         


        4.7 回調(diào)函數(shù)

        使 用輪詢方式來(lái)檢測(cè)異步方法的狀態(tài)非常麻煩,而且效率不高,有見(jiàn)及此,.NET為 IAsyncResult BeginInvoke(AsyncCallback , object)準(zhǔn)備了一個(gè)回調(diào)函數(shù)。使用 AsyncCallback 就可以綁定一個(gè)方法作為回調(diào)函數(shù),回調(diào)函數(shù)必須是帶參數(shù) IAsyncResult 且無(wú)返回值的方法: void AsycnCallbackMethod(IAsyncResult result) 。在BeginInvoke方法完成后,系統(tǒng)就會(huì)調(diào)用AsyncCallback所綁定的回調(diào)函數(shù),最后回調(diào)函數(shù)中調(diào)用 XXX EndInvoke(IAsyncResult result) 就可以結(jié)束異步方法,它的返回值類(lèi)型與委托的返回值一致。

         1     class Program
         2     {
         3         delegate string MyDelegate(string name);
         4 
         5         static void Main(string[] args)
         6         {
         7             ThreadMessage("Main Thread");
         8 
         9             //建立委托
        10 MyDelegate myDelegate = new MyDelegate(Hello); 11 //異步調(diào)用委托,獲取計(jì)算結(jié)果
        12 myDelegate.BeginInvoke("Leslie", new AsyncCallback(Completed), null); 13 //在啟動(dòng)異步線程后,主線程可以繼續(xù)工作而不需要等待
        14 for (int n = 0; n < 6; n++) 15 Console.WriteLine(" Main thread do work!"); 16 Console.WriteLine(""); 17 18 Console.ReadKey(); 19 } 20 21 static string Hello(string name) 22 { 23 ThreadMessage("Async Thread"); 24 Thread.Sleep(2000); \\模擬異步操作 25 return "\nHello " + name; 26 } 27 28 static void Completed(IAsyncResult result) 29 { 30 ThreadMessage("Async Completed"); 31 32 //獲取委托對(duì)象,調(diào)用EndInvoke方法獲取運(yùn)行結(jié)果
        33 AsyncResult _result = (AsyncResult)result; 34 MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate; 35 string data = myDelegate.EndInvoke(_result); 36 Console.WriteLine(data); 37 } 38 39 static void ThreadMessage(string data) 40 { 41 string message = string.Format("{0}\n ThreadId is:{1}", 42 data, Thread.CurrentThread.ManagedThreadId); 43 Console.WriteLine(message); 44 } 45 }


        可以看到,主線在調(diào)用BeginInvoke方法可以繼續(xù)執(zhí)行其他命令,而無(wú)需再等待了,這無(wú)疑比使用輪詢方式判斷異步方法是否完成更有優(yōu)勢(shì)。
        在異步方法執(zhí)行完成后將會(huì)調(diào)用AsyncCallback所綁定的回調(diào)函數(shù),注意一點(diǎn),回調(diào)函數(shù)依然是在異步線程中執(zhí)行,這樣就不會(huì)影響主線程的運(yùn)行,這也使用回調(diào)函數(shù)最值得青昧的地方。
        在回調(diào)函數(shù)中有一個(gè)既定的參數(shù)IAsyncResult,把IAsyncResult強(qiáng)制轉(zhuǎn)換為AsyncResult后,就可以通過(guò) AsyncResult.AsyncDelegate 獲取原委托,再使用EndInvoke方法獲取計(jì)算結(jié)果。
        運(yùn)行結(jié)果如下:


        如 果想為回調(diào)函數(shù)傳送一些外部信息,就可以利用BeginInvoke(AsyncCallback,object)的最后一個(gè)參數(shù)object,它允許外 部向回調(diào)函數(shù)輸入任何類(lèi)型的參數(shù)。只需要在回調(diào)函數(shù)中利用 AsyncResult.AsyncState 就可以獲取object對(duì)象。

         1     class Program
         2     {
         3         public class Person
         4         {
         5             public string Name;
         6             public int Age;
         7         }
         8 
         9         delegate string MyDelegate(string name);
        10 
        11         static void Main(string[] args)
        12         {
        13             ThreadMessage("Main Thread");
        14 
        15             //建立委托
        16 MyDelegate myDelegate = new MyDelegate(Hello); 17 18 //建立Person對(duì)象
        19 Person person = new Person(); 20 person.Name = "Elva"; 21 person.Age = 27; 22 23 //異步調(diào)用委托,輸入?yún)?shù)對(duì)象person, 獲取計(jì)算結(jié)果
        24 myDelegate.BeginInvoke("Leslie", new AsyncCallback(Completed), person); 25 26 //在啟動(dòng)異步線程后,主線程可以繼續(xù)工作而不需要等待
        27 for (int n = 0; n < 6; n++) 28 Console.WriteLine(" Main thread do work!"); 29 Console.WriteLine(""); 30 31 Console.ReadKey(); 32 } 33 34 static string Hello(string name) 35 { 36 ThreadMessage("Async Thread"); 37 Thread.Sleep(2000); 38 return "\nHello " + name; 39 } 40 41 static void Completed(IAsyncResult result) 42 { 43 ThreadMessage("Async Completed"); 44 45 //獲取委托對(duì)象,調(diào)用EndInvoke方法獲取運(yùn)行結(jié)果
        46 AsyncResult _result = (AsyncResult)result; 47 MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate; 48 string data = myDelegate.EndInvoke(_result); 49 //獲取Person對(duì)象
        50 Person person = (Person)result.AsyncState; 51 string message = person.Name + "'s age is " + person.Age.ToString(); 52 53 Console.WriteLine(data+"\n"+message); 54 } 55 56 static void ThreadMessage(string data) 57 { 58 string message = string.Format("{0}\n ThreadId is:{1}", 59 data, Thread.CurrentThread.ManagedThreadId); 60 Console.WriteLine(message); 61 } 62 } 億恩-天使(QQ:530997) 電話 037160135991 服務(wù)器租用,托管歡迎咨詢。

        本文出自:億恩科技【mszdt.com】

        服務(wù)器租用/服務(wù)器托管中國(guó)五強(qiáng)!虛擬主機(jī)域名注冊(cè)頂級(jí)提供商!15年品質(zhì)保障!--億恩科技[ENKJ.COM]

      2. 您可能在找
      3. 億恩北京公司:
      4. 經(jīng)營(yíng)性ICP/ISP證:京B2-20150015
      5. 億恩鄭州公司:
      6. 經(jīng)營(yíng)性ICP/ISP/IDC證:豫B1.B2-20060070
      7. 億恩南昌公司:
      8. 經(jīng)營(yíng)性ICP/ISP證:贛B2-20080012
      9. 服務(wù)器/云主機(jī) 24小時(shí)售后服務(wù)電話:0371-60135900
      10. 虛擬主機(jī)/智能建站 24小時(shí)售后服務(wù)電話:0371-60135900
      11. 專注服務(wù)器托管17年
        掃掃關(guān)注-微信公眾號(hào)
        0371-60135900
        Copyright© 1999-2019 ENKJ All Rights Reserved 億恩科技 版權(quán)所有  地址:鄭州市高新區(qū)翠竹街1號(hào)總部企業(yè)基地億恩大廈  法律顧問(wèn):河南亞太人律師事務(wù)所郝建鋒、杜慧月律師   京公網(wǎng)安備41019702002023號(hào)
          0
         
         
         
         

        0371-60135900
        7*24小時(shí)客服服務(wù)熱線