搜尋
首頁後端開發C#.Net教程C#綜合揭密—細說多線程

C#綜合揭密—細說多線程

Dec 12, 2016 pm 03:06 PM
c#多線程

本文主要從執行緒的基礎用法,CLR執行緒池當中工作者執行緒與I/O執行緒的開發,並行操作PLINQ等多個面向介紹多執行緒的開發。
其中委託的BeginInvoke方法以及回呼函數最為常用。
而 I/O執行緒可能容易遭到大家的忽略,其實在開發多執行緒系統,更應該多留意I/O執行緒的操作。特別是在ASP.NET開發當中,可能更多人只會留意在客戶端使用Ajax或是在伺服器端使用UpdatePanel。其實合理使用I/O線程在通訊項目或文件下載時,能盡量減少IIS的壓力。
平行程式設計是Framework4.0中極力推廣的非同步操作方式,更值得更深入學習。
希望這篇文章能對各位的學習研究有所幫助,當中有所錯漏的地方敬請點評。

一、執行緒的定義

 1. 1 進程、應用程式域與執行緒的關係

進程(Process)是Windows系統中的一個基本概念,它包含一個執行程式所需的資源。行程之間是相對獨立的,一個行程無法存取另一個行程的資料(除非利用分散式運算方式),一個行程運行的失敗不會影響其他行程的執行,Windows系統就是利用行程把工作分成多個獨立的區域的。進程可以理解為一個程式的基本邊界。

應用程式域(AppDomain)是一個程式運行的邏輯區域,它可以視為一個輕量級的進程,.NET的組件正是在應用程式域中運行的,一個進程可以包含有多個應用程式域,一個應用程式域也可以包含多個組件。在一個應用程式域中包含了一個或多個上下文context,使用上下文CLR就能夠把某些特殊物件的狀態放置在不同容器當中。

線程(Thread)是進程中的基本執行單元,在進程入口執行的第一個執行緒被視為這個進程的主執行緒。在.NET應用程式中,都是以Main()方法作為入口的,當系統呼叫此方法時系統就會自動建立一個主執行緒。執行緒主要是由CPU暫存器、呼叫堆疊和執行緒本地記憶體(Thread Local Storage,TLS)組成的。 CPU暫存器主要記錄目前所執行緒的狀態,呼叫堆疊主要用於維護執行緒所呼叫的記憶體與數據,TLS主要用於存放執行緒的狀態資訊。

行程、應用程式域、執行緒的關係如下圖,一個行程內可以包含多個應用程式域,也有包含多個線程,執行緒也可以穿梭於多個應用程式域當中。但在同一個時刻,執行緒只會處於一個應用程式域內。

C#綜合揭密—細說多線程

1.2 多執行緒

在單CPU系統的一個單位時間(time slice)內,CPU只能運行單一線程,運行順序取決於執行緒的優先權。如果在單位時間內執行緒未能完成執行,系統就會把執行緒的狀態資訊儲存到執行緒的本機記憶體(TLS) 中,以便下次執行時恢復執行。而多執行緒只是系統帶來的一個假像,它在多個單位時間內進行多個執行緒的切換。因為切換頻密而且單位時間非常短暫,所以多執行緒可被視為同時運作。

適當使用多線程能提高系統的效能,例如:在系統請求大容量的資料時使用多線程,把資料輸出工作交給非同步線程,使主線程保持其穩定性去處理其他問題。但需要注意一點,因為CPU需要花費不少的時間在執行緒的切換上,所以過度使用多執行緒反而會導致效能的下降。

二、執行緒的基礎知識

2.1 System.Threading.Thread類

System.Threading.Thread是用於控制執行緒的基礎類,透過Thread可以控制目前應用程式域中執行緒的建立、掛起、停止、銷毀。

它包括以下常用公共屬性:

C#綜合揭密—細說多線程

2.1.1 執行緒的識別符

ManagedThreadId是確認執行緒的唯一標識符,程式在大部分情況下都是透過Thread.ManagedThread來辨別執行的。而Name是一個可變值,在預設時候,Name為一個空值 Null,開發人員可以透過程式設定執行緒的名稱,但這只是一個輔助功能。

 

2.1.2 執行緒的優先等級

.NET為執行緒設定了Priority屬性來定義執行緒執行的優先級別,內麵包含5個選項,其中Normal是預設值。除非系統有特殊要求,否則不應該隨便設定執行緒的優先等級。

C#綜合揭密—細說多線程

2.1.3 執行緒的狀態

透過ThreadState可以偵測執行緒是處於Unstarted、Sleeping、Running 等等狀態,它比 IsAlive 屬性能提供更多的特定資訊。

前面說過,一個應用程式域中可能包含多個上下文,而透過CurrentContext可以取得執行緒目前的上下文。

CurrentThread是最常用的屬性,它是用來取得目前執行的執行緒。

 

2.1.4 System.Threading.Thread的方法

Thread 中包含了多個方法來控制執行緒的建立、掛起、停止、銷毀,以後來的例子中會經常使用。

C#綜合揭密—細說多線程

2.1.5 開發實例

以下這個例子,就是透過Thread顯示當前執行緒資訊

static void Main(string[] args)
        {
            Thread thread = Thread.CurrentThread;
            thread.Name = "Main Thread";
            string threadMessage = string.Format("Thread ID:{0}\n    Current AppDomainId:{1}\n    "+
                "Current ContextId:{2}\n    Thread Name:{3}\n    "+
                "Thread State:{4}\n    Thread Priority:{5}\n",
                thread.ManagedThreadId, Thread.GetDomainID(), Thread.CurrentContext.ContextID,
                thread.Name, thread.ThreadState, thread.Priority);
            Console.WriteLine(threadMessage);
            Console.ReadKey();
        }

運行結果

C#綜合揭密—細說多線程

yst32.2.2個方法來建構多執行緒應用程式,其中ThreadPool與Thread是多執行緒開發中最常用到的,在.NET中專門設定了一個CLR執行緒池專門用於管理執行緒的運行,這個CLR執行緒池正是透過ThreadPool類別來管理。而Thread是管理執行緒最直接的方式,以下幾節將詳細介紹有關內容。

在System.Threading中的包含了下表中的多個常用委託,其中ThreadStart、ParameterizedThreadStart是最常用到的委託。 C#綜合揭密—細說多線程由ThreadStart產生的執行緒是最直接的方式,但由ThreadStart產生並不受執行緒池管理。

而ParameterizedThreadStart是為非同步觸發帶參數的方法而設的,在下一節將為大家逐一細說。



2.3 執行緒的管理方式C#綜合揭密—細說多線程

透過ThreadStart來建立一個新執行緒是最直接的方法,但這樣建立出來的執行緒比較難管理,如果建立過多的執行緒反而會讓系統的效能下載。有見及此,.NET為執行緒管理專門設定了一個CLR執行緒池,使用CLR執行緒池系統可以更合理地管理執行緒的使用。所有請求的服務都能運行於執行緒池中,當執行結束時執行緒便會回歸到執行緒池。透過設置,能控制執行緒池的最大執行緒數量,在請求超出執行緒最大值時,執行緒池能依照操作的優先權來執行,讓部分操作處於等待狀態,待有執行緒回歸時再執行操作。

基礎知識就為大家介紹到這裡,以下將詳細介紹多執行緒的開發。

三、以ThreadStart方式實現多執行緒

3.1 使用ThreadStart委託

這裡先以一個例子體現一下多執行緒帶來的好處,首先在Message類別中建立一個方法ShowMessage(),裡面顯示了當前運行執行緒的Id,並使用Thread.Sleep(int ) 方法模擬部分工作。在main()中透過ThreadStart委託綁定Message物件的ShowMessage()方法,然後透過Thread.Start()執行非同步方法。

public class Message
      {
          public void ShowMessage()
          {
              string message = string.Format("Async threadId is :{0}",
                                              Thread.CurrentThread.ManagedThreadId);
              Console.WriteLine(message);
  
              for (int n = 0; n < 10; n++)
              {
                  Thread.Sleep(300);   
                  Console.WriteLine("The number is:" + n.ToString()); 
              }
          }
      }
  
      class Program
      {
          static void Main(string[] args)
          {
              Console.WriteLine("Main threadId is:"+
                                Thread.CurrentThread.ManagedThreadId);
              Message message=new Message();
              Thread thread = new Thread(new ThreadStart(message.ShowMessage));
              thread.Start();
              Console.WriteLine("Do something ..........!");
              Console.WriteLine("Main thread working is complete!");
              
          }
      }

請注意運行結果,在呼叫Thread.Start()方法後,系統以非同步方式運行Message.ShowMessage(),而主執行緒的操作是繼續執行的,在Message.ShowMessage()完成之前,主執行緒已完成所有的操作。

3.2 使用ParameterizedThreadStart委託C#綜合揭密—細說多線程

ParameterizedThreadStart委託與ThreadStart委託非常相似,但ParameterizedThreadStart委託是面向帶參數方法的。注意ParameterizedThreadStart 對應方法的參數為object,此參數可以為一個值對象,也可以為一個自訂對象。

public class Person
    {
        public string Name
        {
            get;
            set;
        }
        public int Age
        {
            get;
            set;
        }
    }

    public class Message
    {
        public void ShowMessage(object person)
        {
            if (person != null)
            {
                Person _person = (Person)person;
                string message = string.Format("\n{0}&#39;s age is {1}!\nAsync threadId is:{2}",
                    _person.Name,_person.Age,Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine(message);
            }
            for (int n = 0; n < 10; n++)
            {
                Thread.Sleep(300);   
                Console.WriteLine("The number is:" + n.ToString()); 
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {     
            Console.WriteLine("Main threadId is:"+Thread.CurrentThread.ManagedThreadId);
            
            Message message=new Message();
            //绑定带参数的异步方法
            Thread thread = new Thread(new ParameterizedThreadStart(message.ShowMessage));
            Person person = new Person();
            person.Name = "Jack";
            person.Age = 21;
            thread.Start(person);  //启动异步线程 
            
            Console.WriteLine("Do something ..........!");
            Console.WriteLine("Main thread working is complete!");
             
        }
    }

運行結果:


3.3 前台線程與後台線程C#綜合揭密—細說多線程

注意以上兩個例子都沒有使用Console.ReadKey(),但係統依然會等待異步完成後才會結束執行緒。這是因為使用Thread.Start()啟動的執行緒預設為前台線程,而係統必須等待所有前台執行緒運行結束後,應用程式網域才會自動卸載。

在第二節曾經介紹過線程Thread有一個屬性IsBackground,透過把這個屬性設為true,就可以把執行緒設定為後台執行緒!這時應用程式域將在主執行緒完成時就被卸載,而不會等待非同步執行緒的運行。

 

3.4 掛起線程

為了等待其他後台線程完成後再結束主線程,就可以使用Thread.Sleep()方法。

public class Message
    {
        public void ShowMessage()
        {
            string message = string.Format("\nAsync threadId is:{0}",
                                           Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
            for (int n = 0; n < 10; n++)
            {
                Thread.Sleep(300);
                Console.WriteLine("The number is:" + n.ToString());
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {     
            Console.WriteLine("Main threadId is:"+
                              Thread.CurrentThread.ManagedThreadId);
            
            Message message=new Message();
            Thread thread = new Thread(new ThreadStart(message.ShowMessage));
            thread.IsBackground = true;
            thread.Start();
            
            Console.WriteLine("Do something ..........!");
            Console.WriteLine("Main thread working is complete!");
            Console.WriteLine("Main thread sleep!");
            Thread.Sleep(5000);
        }
    }

運行結果如下,此時應用程式域將在主執行緒執行5秒後自動結束

但系统无法预知异步线程需要运行的时间,所以用通过Thread.Sleep(int)阻塞主线程并不是一个好的解决方法。有见及此,.NET专门为等待异步线程完成开发了另一个方法thread.Join()。把上面例子中的最后一行Thread.Sleep(5000)修改为 thread.Join() 就能保证主线程在异步线程thread运行结束后才会终止。

 

3.5 Suspend 与 Resume (慎用)

Thread.Suspend()与 Thread.Resume()是在Framework1.0 就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend()使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。

 

3.6 终止线程

若想终止正在运行的线程,可以使用Abort()方法。在使用Abort()的时候,将引发一个特殊异常 ThreadAbortException 。
若想在线程终止前恢复线程的执行,可以在捕获异常后 ,在catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。
而使用Thread.Join()可以保证应用程序域等待异步线程结束后才终止运行。

static void Main(string[] args)
         {
             Console.WriteLine("Main threadId is:" +
                               Thread.CurrentThread.ManagedThreadId);
 
             Thread thread = new Thread(new ThreadStart(AsyncThread));
             thread.IsBackground = true;
             thread.Start();
             thread.Join();
 
         }     
         
         //以异步方式调用
         static void AsyncThread()
         {
             try
             {
                 string message = string.Format("\nAsync threadId is:{0}",
                    Thread.CurrentThread.ManagedThreadId);
                 Console.WriteLine(message);
 
                 for (int n = 0; n < 10; n++)
                 {
                     //当n等于4时,终止线程
                     if (n >= 4)
                     {
                         Thread.CurrentThread.Abort(n);
                     }
                     Thread.Sleep(300);
                     Console.WriteLine("The number is:" + n.ToString());
                 }
             }
             catch (ThreadAbortException ex)
             {
                 //输出终止线程时n的值
                 if (ex.ExceptionState != null)
                     Console.WriteLine(string.Format("Thread abort when the number is: {0}!", 
                                                      ex.ExceptionState.ToString()));
                
                 //取消终止,继续执行线程
                 Thread.ResetAbort();
                 Console.WriteLine("Thread ResetAbort!");
             }
 
             //线程结束
             Console.WriteLine("Thread Close!");
         }

运行结果如下

C#綜合揭密—細說多線程

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

4.1 关于CLR线程池

使用ThreadStart与ParameterizedThreadStart建立新线程非常简单,但通过此方法建立的线程难于管理,若建立过多的线程反而会影响系统的性能。
有见及此,.NET引入CLR线程池这个概念。CLR线程池并不会在CLR初始化的时候立刻建立线程,而是在应用程序要创建线程来执行任务时,线程池才初始化一个线程。线程的初始化与其他的线程一样。在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。

注意:通过CLR线程池所建立的线程总是默认为后台线程,优先级数为ThreadPriority.Normal。

 

4.2 工作者线程与I/O线程

CLR线程池分为工作者线程(workerThreads)与I/O线程 (completionPortThreads) 两种,工作者线程是主要用作管理CLR内部对象的运作,I/O(Input/Output) 线程顾名思义是用于与外部系统交换信息,IO线程的细节将在下一节详细说明。

通过ThreadPool.GetMax(out int workerThreads,out int completionPortThreads )和 ThreadPool.SetMax( int workerThreads, int completionPortThreads)两个方法可以分别读取和设置CLR线程池中工作者线程与I/O线程的最大线程数。在Framework2.0中最大线程默认为25*CPU数,在Framewok3.0、4.0中最大线程数默认为250*CPU数,在近年 I3,I5,I7 CPU出现后,线程池的最大值一般默认为1000、2000。
若想测试线程池中有多少的线程正在投入使用,可以通过ThreadPool.GetAvailableThreads( out intworkerThreads,out int completionPortThreads ) 方法。

使用CLR线程池的工作者线程一般有两种方式,一是直接通过 ThreadPool.QueueUserWorkItem() 方法,二是通过委托,下面将逐一细说。

 

4.3 通过QueueUserWorkItem启动工作者线程

ThreadPool线程池中包含有两个静态方法可以直接启动工作者线程:
一为 ThreadPool.QueueUserWorkItem(WaitCallback)
二为 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 

先把WaitCallback委托指向一个带有Object参数的无返回值方法,再使用 ThreadPool.QueueUserWorkItem(WaitCallback) 就可以异步启动此方法,此时异步方法的参数被视为null 。

class Program
    {
        static void Main(string[] args)
        {
            //把CLR线程池的最大值设置为1000
            ThreadPool.SetMaxThreads(1000, 1000);
            //显示主线程启动时线程池信息
            ThreadMessage("Start");
            //启动工作者线程
            ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback));
            Console.ReadKey();
        }
        
        static void AsyncCallback(object state)
        {
            Thread.Sleep(200);
            ThreadMessage("AsyncCallback");
            Console.WriteLine("Async thread do work!");
        }

        //显示线程现状
        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  CurrentThreadId is {1}",
                 data, Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

运行结果

C#綜合揭密—細說多線程

使用 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 方法可以把object对象作为参数传送到回调函数中。
下面例子中就是把一个string对象作为参数发送到回调函数当中。

class Program
    {
        static void Main(string[] args)
        {
            //把线程池的最大值设置为1000
            ThreadPool.SetMaxThreads(1000, 1000);
          
            ThreadMessage("Start");
            ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback),"Hello Elva");
            Console.ReadKey();
        }

        static void AsyncCallback(object state)
        {
            Thread.Sleep(200);
            ThreadMessage("AsyncCallback");

            string data = (string)state;
            Console.WriteLine("Async thread do work!\n"+data);
        }

        //显示线程现状
        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  CurrentThreadId is {1}",
                 data, Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

运行结果

C#綜合揭密—細說多線程

通过ThreadPool.QueueUserWorkItem启动工作者线程虽然是方便,但WaitCallback委托指向的必须是一个带有Object参数的无返回值方法,这无疑是一种限制。若方法需要有返回值,或者带有多个参数,这将多费周折。有见及此,.NET提供了另一种方式去建立工作者线程,那就是委托。

 

4.4  委托类       

使用CLR线程池中的工作者线程,最灵活最常用的方式就是使用委托的异步方法,在此先简单介绍一下委托类。

当定义委托后,.NET就会自动创建一个代表该委托的类,下面可以用反射方式显示委托类的方法成员(对反射有兴趣的朋友可以先参考一下“.NET基础篇——反射的奥妙”)

class Program
    {
        delegate void MyDelegate();

        static void Main(string[] args)
        {
            MyDelegate delegate1 = new MyDelegate(AsyncThread);
            //显示委托类的几个方法成员     
            var methods=delegate1.GetType().GetMethods();
            if (methods != null)
                foreach (MethodInfo info in methods)
                    Console.WriteLine(info.Name);
            Console.ReadKey();
         }
     }

委托类包括以下几个重要方法

C#綜合揭密—細說多線程

public class MyDelegate:MulticastDelegate
    {
        public MyDelegate(object target, int methodPtr);
        //调用委托方法
        public virtual void Invoke();
        //异步委托
        public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state);
        public virtual void EndInvoke(IAsyncResult result);
    }

当调用Invoke()方法时,对应此委托的所有方法都会被执行。而BeginInvoke与EndInvoke则支持委托方法的异步调用,由BeginInvoke启动的线程都属于CLR线程池中的工作者线程,在下面将详细说明。

 

4.5  利用BeginInvoke与EndInvoke完成异步委托方法

首先建立一个委托对象,通过IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state) 异步调用委托方法,BeginInvoke 方法除最后的两个参数外,其它参数都是与方法参数相对应的。通过 BeginInvoke 方法将返回一个实现了 System.IAsyncResult 接口的对象,之后就可以利用EndInvoke(IAsyncResult ) 方法就可以结束异步操作,获取委托的运行结果。

class Program
    {
        delegate string MyDelegate(string name);

        static void Main(string[] args)
        {
            ThreadMessage("Main Thread");
            
            //建立委托
            MyDelegate myDelegate = new MyDelegate(Hello);
            //异步调用委托,获取计算结果
            IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
            //完成主线程其他工作
            ............. 
            //等待异步方法完成,调用EndInvoke(IAsyncResult)获取运行结果
            string data=myDelegate.EndInvoke(result);
            Console.WriteLine(data);
            
            Console.ReadKey();
        }

        static string Hello(string name)
        {
            ThreadMessage("Async Thread");
            Thread.Sleep(2000);            //虚拟异步工作
            return "Hello " + name;
        }

        //显示当前线程
        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  ThreadId is:{1}",
                   data,Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

运行结果

C#綜合揭密—細說多線程

4.6  善用IAsyncResult

在以上例子中可以看见,如果在使用myDelegate.BeginInvoke后立即调用myDelegate.EndInvoke,那在异步线程未完成工作以前主线程将处于阻塞状态,等到异步线程结束获取计算结果后,主线程才能继续工作,这明显无法展示出多线程的优势。此时可以好好利用IAsyncResult 提高主线程的工作性能,IAsyncResult有以下成员

public interface IAsyncResult
{
    object AsyncState {get;}            //获取用户定义的对象,它限定或包含关于异步操作的信息。
    WailHandle AsyncWaitHandle {get;}   //获取用于等待异步操作完成的 WaitHandle。
    bool CompletedSynchronously {get;}  //获取异步操作是否同步完成的指示。
    bool IsCompleted {get;}             //获取异步操作是否已完成的指示。
}

通过轮询方式,使用IsCompleted属性判断异步操作是否完成,这样在异步操作未完成前就可以让主线程执行另外的工作。

class Program
    {
        delegate string MyDelegate(string name);

        static void Main(string[] args)
        {
            ThreadMessage("Main Thread");
            
            //建立委托
            MyDelegate myDelegate = new MyDelegate(Hello);
            //异步调用委托,获取计算结果
            IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
            //在异步线程未完成前执行其他工作
            while (!result.IsCompleted)
            {
                Thread.Sleep(200);      //虚拟操作
                Console.WriteLine("Main thead do work!");
            }
            string data=myDelegate.EndInvoke(result);
            Console.WriteLine(data);
            
            Console.ReadKey();
        }

        static string Hello(string name)
        {
            ThreadMessage("Async Thread");
            Thread.Sleep(2000);
            return "Hello " + name;
        }

        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  ThreadId is:{1}",
                   data,Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

运行结果:

C#綜合揭密—細說多線程

除此以外,也可以使用WailHandle完成同样的工作,WaitHandle里面包含有一个方法WaitOne(int timeout),它可以判断委托是否完成工作,在工作未完成前主线程可以继续其他工作。运行下面代码可得到与使用 IAsyncResult.IsCompleted 同样的结果,而且更简单方便 。

namespace Test
{
    class Program
    {
        delegate string MyDelegate(string name);

        static void Main(string[] args)
        {
            ThreadMessage("Main Thread");
            
            //建立委托
            MyDelegate myDelegate = new MyDelegate(Hello);
 
            //异步调用委托,获取计算结果
            IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
            
            while (!result.AsyncWaitHandle.WaitOne(200))
            {
                Console.WriteLine("Main thead do work!");
            }
            string data=myDelegate.EndInvoke(result);
            Console.WriteLine(data);
            
            Console.ReadKey();
        }

        static string Hello(string name)
        {
            ThreadMessage("Async Thread");
            Thread.Sleep(2000);
            return "Hello " + name;
        }

        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  ThreadId is:{1}",
                   data,Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

当要监视多个运行对象的时候,使用IAsyncResult.WaitHandle.WaitOne可就派不上用场了。
幸好.NET为WaitHandle准备了另外两个静态方法:WaitAny(waitHandle[], int)与WaitAll (waitHandle[] , int)。
其中WaitAll在等待所有waitHandle完成后再返回一个bool值。
而WaitAny是等待其中一个waitHandle完成后就返回一个int,这个int是代表已完成waitHandle在waitHandle[]中的数组索引。
下面就是使用WaitAll的例子,运行结果与使用 IAsyncResult.IsCompleted 相同。

class Program
    {
        delegate string MyDelegate(string name);

        static void Main(string[] args)
        {
            ThreadMessage("Main Thread");
            
            //建立委托
            MyDelegate myDelegate = new MyDelegate(Hello);
 
            //异步调用委托,获取计算结果
            IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);

            //此处可加入多个检测对象
            WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle,........ };
            while (!WaitHandle.WaitAll(waitHandleList,200))
            {
                Console.WriteLine("Main thead do work!");
            }
            string data=myDelegate.EndInvoke(result);
            Console.WriteLine(data);
            
            Console.ReadKey();
        }

        static string Hello(string name)
        {
            ThreadMessage("Async Thread");
            Thread.Sleep(2000);
            return "Hello " + name;
        }

        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  ThreadId is:{1}",
                   data,Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

4.7 回调函数

使用轮询方式来检测异步方法的状态非常麻烦,而且效率不高,有见及此,.NET为 IAsyncResult BeginInvoke(AsyncCallback , object)准备了一个回调函数。使用 AsyncCallback 就可以绑定一个方法作为回调函数,回调函数必须是带参数 IAsyncResult 且无返回值的方法: void AsycnCallbackMethod(IAsyncResult result) 。在BeginInvoke方法完成后,系统就会调用AsyncCallback所绑定的回调函数,最后回调函数中调用 XXX EndInvoke(IAsyncResult result) 就可以结束异步方法,它的返回值类型与委托的返回值一致。

class Program
    {
        delegate string MyDelegate(string name);

        static void Main(string[] args)
        {
            ThreadMessage("Main Thread");

            //建立委托
            MyDelegate myDelegate = new MyDelegate(Hello);
            //异步调用委托,获取计算结果
            myDelegate.BeginInvoke("Leslie", new AsyncCallback(Completed), null);
            //在启动异步线程后,主线程可以继续工作而不需要等待
            for (int n = 0; n < 6; n++)
                Console.WriteLine("  Main thread do work!");
            Console.WriteLine("");

            Console.ReadKey();
        }

        static string Hello(string name)
        {
            ThreadMessage("Async Thread");
            Thread.Sleep(2000);             \\模拟异步操作
            return "\nHello " + name;
        }

        static void Completed(IAsyncResult result)
        {
            ThreadMessage("Async Completed");

            //获取委托对象,调用EndInvoke方法获取运行结果
            AsyncResult _result = (AsyncResult)result;
            MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate;
            string data = myDelegate.EndInvoke(_result);
            Console.WriteLine(data);
        }

        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  ThreadId is:{1}",
                   data, Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

可以看到,主线在调用BeginInvoke方法可以继续执行其他命令,而无需再等待了,这无疑比使用轮询方式判断异步方法是否完成更有优势。
在异步方法执行完成后将会调用AsyncCallback所绑定的回调函数,注意一点,回调函数依然是在异步线程中执行,这样就不会影响主线程的运行,这也使用回调函数最值得青昧的地方。
在回调函数中有一个既定的参数IAsyncResult,把IAsyncResult强制转换为AsyncResult后,就可以通过 AsyncResult.AsyncDelegate 获取原委托,再使用EndInvoke方法获取计算结果。
运行结果如下:

C#綜合揭密—細說多線程

如果想为回调函数传送一些外部信息,就可以利用BeginInvoke(AsyncCallback,object)的最后一个参数object,它允许外部向回调函数输入任何类型的参数。只需要在回调函数中利用 AsyncResult.AsyncState 就可以获取object对象。

class Program
    {
        public class Person
        {
            public string Name;
            public int Age;
        }

        delegate string MyDelegate(string name);

        static void Main(string[] args)
        {
            ThreadMessage("Main Thread");

            //建立委托
            MyDelegate myDelegate = new MyDelegate(Hello);
            
            //建立Person对象
            Person person = new Person();
            person.Name = "Elva";
            person.Age = 27;
            
            //异步调用委托,输入参数对象person, 获取计算结果
            myDelegate.BeginInvoke("Leslie", new AsyncCallback(Completed), person);            
          
            //在启动异步线程后,主线程可以继续工作而不需要等待
            for (int n = 0; n < 6; n++)
                Console.WriteLine("  Main thread do work!");
            Console.WriteLine("");

            Console.ReadKey();
        }

        static string Hello(string name)
        {
            ThreadMessage("Async Thread");
            Thread.Sleep(2000);
            return "\nHello " + name;
        }

        static void Completed(IAsyncResult result)
        {
            ThreadMessage("Async Completed");

            //获取委托对象,调用EndInvoke方法获取运行结果
            AsyncResult _result = (AsyncResult)result;
            MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate;
            string data = myDelegate.EndInvoke(_result);
            //获取Person对象
            Person person = (Person)result.AsyncState;
            string message = person.Name + "&#39;s age is " + person.Age.ToString();

            Console.WriteLine(data+"\n"+message);
        }

        static void ThreadMessage(string data)
        {
            string message = string.Format("{0}\n  ThreadId is:{1}",
                   data, Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(message);
        }
    }

运行结果:

C#綜合揭密—細說多線程

关于I/O线程、SqlCommand多线程查询、PLINQ、定时器与锁的内容将在C#综合揭秘——细说多线程(下)中详细介绍。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
C#.NET生態系統:框架,庫和工具C#.NET生態系統:框架,庫和工具Apr 24, 2025 am 12:02 AM

C#.NET生態系統提供了豐富的框架和庫,幫助開發者高效構建應用。 1.ASP.NETCore用於構建高性能Web應用,2.EntityFrameworkCore用於數據庫操作。通過理解這些工具的使用和最佳實踐,開發者可以提高應用的質量和性能。

將C#.NET應用程序部署到Azure/AWS:逐步指南將C#.NET應用程序部署到Azure/AWS:逐步指南Apr 23, 2025 am 12:06 AM

如何將C#.NET應用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。 1.在Azure上,使用AzureAppService和AzurePipelines自動化部署。 2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda實現部署和無服務器計算。

C#.NET:強大的編程語言簡介C#.NET:強大的編程語言簡介Apr 22, 2025 am 12:04 AM

C#和.NET的結合為開發者提供了強大的編程環境。 1)C#支持多態性和異步編程,2).NET提供跨平台能力和並發處理機制,這使得它們在桌面、Web和移動應用開發中廣泛應用。

.NET框架與C#:解碼術語.NET框架與C#:解碼術語Apr 21, 2025 am 12:05 AM

.NETFramework是一個軟件框架,C#是一種編程語言。 1..NETFramework提供庫和服務,支持桌面、Web和移動應用開發。 2.C#設計用於.NETFramework,支持現代編程功能。 3..NETFramework通過CLR管理代碼執行,C#代碼編譯成IL後由CLR運行。 4.使用.NETFramework可快速開發應用,C#提供如LINQ的高級功能。 5.常見錯誤包括類型轉換和異步編程死鎖,調試需用VisualStudio工具。

揭開c#.net的神秘面紗:初學者的概述揭開c#.net的神秘面紗:初學者的概述Apr 20, 2025 am 12:11 AM

C#是一種由微軟開發的現代、面向對象的編程語言,.NET是微軟提供的開發框架。 C#結合了C 的性能和Java的簡潔性,適用於構建各種應用程序。 .NET框架支持多種語言,提供垃圾回收機制,簡化內存管理。

C#和.NET運行時:它們如何一起工作C#和.NET運行時:它們如何一起工作Apr 19, 2025 am 12:04 AM

C#和.NET運行時緊密合作,賦予開發者高效、強大且跨平台的開發能力。 1)C#是一種類型安全且面向對象的編程語言,旨在與.NET框架無縫集成。 2).NET運行時管理C#代碼的執行,提供垃圾回收、類型安全等服務,確保高效和跨平台運行。

C#.NET開發:入門的初學者指南C#.NET開發:入門的初學者指南Apr 18, 2025 am 12:17 AM

要開始C#.NET開發,你需要:1.了解C#的基礎知識和.NET框架的核心概念;2.掌握變量、數據類型、控制結構、函數和類的基本概念;3.學習C#的高級特性,如LINQ和異步編程;4.熟悉常見錯誤的調試技巧和性能優化方法。通過這些步驟,你可以逐步深入C#.NET的世界,並編寫高效的應用程序。

c#和.net:了解兩者之間的關係c#和.net:了解兩者之間的關係Apr 17, 2025 am 12:07 AM

C#和.NET的關係是密不可分的,但它們不是一回事。 C#是一門編程語言,而.NET是一個開發平台。 C#用於編寫代碼,編譯成.NET的中間語言(IL),由.NET運行時(CLR)執行。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境