C# 多線程編程第一步——理解多線程

1、進程、線程及多線程的概念html

什麼是多線程呢?不理解。編程

那什麼是線程呢?說到線程就不得不說說進程。我在網上搜索也搜索了一些資料,大部分所說的進程實際上是很抽象的東西。通俗的來說,進程就是一個應用程序開始運行,那麼這個應用程序就會存在一個屬於這個應用程序的進程。安全

那麼線程就是進程中的基本執行單元,每一個進程中都至少存在着一個線程,這個線程是根據進程建立而建立的,因此這個線程咱們稱之爲主線程。那麼多線程就是包含有除了主線程以外的其餘線程。若是一個線程能夠執行一個任務,那麼多線程就是能夠同時執行多個任務。多線程

以上的概念純屬我的理解,若有什麼不對的地方,還請多多指正。異步

 

2、線程的基本知識async

Thread 類函數

Thread 類是用於控制線程的基礎類,它存在於 System.Threading 命名空間。經過 Thread 能夠控制當前應用程序域中線程的建立、掛起、中止、銷燬。性能

Thread 一些經常使用屬性:this

Thread 一些經常使用方法:spa

Thread 的優先級:

 

3、多線程的簡單示例

下面就從簡單的多線程開始理解吧,這裏我建立了一個控制檯應用程序。

   class Program
    {
        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //建立一個新的線程
            Thread thread = new Thread(demoClass.Run);

            //設置爲後臺線程
            thread.IsBackground = true;

            //開始線程
            thread.Start();

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());

            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run()
        {
            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

建立一個新的線程還可使用 ThreadStart 委託的方式。以下:

//建立一個委託,並把要執行的方法做爲參數傳遞給這個委託
ThreadStart threadStart = new ThreadStart(demoClass.Run);
Thread thread = new Thread(threadStart);

執行結果:

根據以上的結果咱們能夠分析獲得,主線程建立了一個子線程並啓動了它,可是主線程沒有等到子線程執行完成,而是繼續再往下執行的。

這就涉及到了線程異步或同步的問題了,這個咱們後面再說。

繼續上面的問題,那麼若是我想要等到子線程執行完成以後再繼續主線程的工做呢(固然,我以爲通常不會有這種需求)。

咱們可使用 Join() 這個方法,修改以後的代碼:

   class Program
    {
        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //建立一個新的線程
            Thread thread = new Thread(demoClass.Run);

            //設置爲後臺線程
            thread.IsBackground = true;

            //開始線程
            thread.Start();

            //等待直到線程完成
            thread.Join();

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());

            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run()
        {
            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

執行結果:

上面的代碼相比以前就添加了一句 thread.Join(),它的做用就是用於阻塞後面的線程,直到當前線程完成以後。固然,還有其餘的方法能夠作到,好比咱們如今把 thread.Join() 換成

下面這句代碼。

//掛起當前線程指定的時間
Thread.Sleep(100);

就當前的場景來講,這樣的確能夠知足需求,可是這樣作有一個弊端,就是,當子線程所執行的方法邏輯比較複雜耗時較長的時候,這樣的方式就不必定能夠,雖然能夠修改線程掛起的時間,可是這個執行的時間倒是不定的。因此,Thread.Sleep() 方法通常用來設置多線程之間執行的間隔時間的。

另外,Join() 方法也接受一個參數,該參數用於指定阻塞線程的時間,若是在指定的時間內該線程沒有終止,那麼就返回 false,若是在指定的時間內已終止,那麼就返回 true。

 

上面的這種使用多線程的方式只是簡單的輸出一段內容而已,多數狀況下咱們須要對線程調用的方法傳入參數和接收返回值的,可是上面這種方法是不接受參數而且沒有返回值的,那麼咱們可使用 ParameterizedThreadStart 委託來建立多線程,這個委託能夠接受一個 object 類型的參數,咱們能夠在這上面作文章。

   class Program
    {static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //建立一個委託,並把要執行的方法做爲參數傳遞給這個委託
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
            
            //建立一個新的線程
            Thread thread = new Thread(threadStart);

            //開始線程,並傳入參數
            thread.Start("Brambling");

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run(object obj)
        {
            string name = obj as string;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + name);
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

執行結果:

PS:這裏我沒有加這句代碼了(thread.IsBackground = true,即把當前線程設置爲後臺線程),由於使用 thread.Start() 啓動的線程默認爲前臺線程。那麼前臺線程和後臺線程有什麼區別呢?

前臺線程就是系統會等待全部的前臺線程運行結束後,應用程序域纔會自動卸載。而設置爲後臺線程以後,應用程序域會在主線程執行完成時被卸載,而不會等待異步線程的執行完成。

那麼上面的結果能夠看到在多線程實現了參數的傳遞,但是它也只有一個參數呢。可是它接受的參數是 object 類型的(萬類之源),也就是說既能夠是值類型或引用類型,也能夠是自定義類型。(固然,自定義類型其實也是屬於引用類型的)下面咱們使用自定義類型做爲參數傳遞。

   class Program
    {
        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //建立一個委託,並把要執行的方法做爲參數傳遞給這個委託
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);

            //建立一個新的線程
            Thread thread = new Thread(threadStart);

            UserInfo userInfo = new UserInfo();
            userInfo.Name = "Brambling";
            userInfo.Age = 333;

            //開始線程,並傳入參數
            thread.Start(userInfo);

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run(object obj)
        {
            UserInfo userInfo = (UserInfo)obj;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + userInfo.Name);
            Console.WriteLine("I'm " + userInfo.Age + " years old this year");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

    public class UserInfo
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }

執行結果:

使用自定義類型做爲參數傳遞,理論上更多個參數也都是能夠實現的。

 

4、線程池

使用 ThreadStart 和 ParameterizedThreadStart 建立線程仍是比較簡單的,可是因爲線程的建立和銷燬須要耗費必定的開銷,過多的使用線程反而會形成內存資源的浪費,從而影響性能,出於對性能的考慮,因而引入了線程池的概念。線程池並非在 CLR 初始化的時候馬上建立線程的,而是在應用程序要建立線程來執行任務的時候,線程池纔會初始化一個線程,初始化的線程和其餘線程同樣,可是在線程完成任務以後不會自行銷燬,而是以掛起的狀態回到線程池。當應用程序再次向現成池發出請求的時候,線程池裏掛起的線程會再度激活執行任務。這樣作能夠減小線程建立和銷燬所帶來的開銷。線程池創建的線程默認爲後臺線程

   class Program
    {
        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //設置當沒有請求時線程池維護的空閒線程數
            //第一個參數爲輔助線程數
            //第二個參數爲異步 I/O 線程數
            ThreadPool.SetMinThreads(5, 5);

            //設置同時處於活動狀態的線程池的線程數,全部大於次數目的請求將保持排隊狀態,直到線程池變爲可用
            //第一個參數爲輔助線程數
            //第二個參數爲異步 I/O 線程數
            ThreadPool.SetMaxThreads(100, 100);

            //使用委託綁定線程池要執行的方法(無參數)
            WaitCallback waitCallback1 = new WaitCallback(demoClass.Run1);
            //將方法排入隊列,在線程池變爲可用時執行
            ThreadPool.QueueUserWorkItem(waitCallback1);


            //使用委託綁定線程池要執行的方法(有參數)
            WaitCallback waitCallback2 = new WaitCallback(demoClass.Run1);
            //將方法排入隊列,在線程池變爲可用時執行
            ThreadPool.QueueUserWorkItem(waitCallback2,"Brambling");


            UserInfo userInfo = new UserInfo();
            userInfo.Name = "Brambling";
            userInfo.Age = 33;

            //使用委託綁定線程池要執行的方法(有參數,自定義類型的參數)
            WaitCallback waitCallback3 = new WaitCallback(demoClass.Run2);
            //將方法排入隊列,在線程池變爲可用時執行
            ThreadPool.QueueUserWorkItem(waitCallback3, userInfo);

            Console.WriteLine();
            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run1(object obj)
        {
            string name = obj as string;

            Console.WriteLine();
            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + name);
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }

        public void Run2(object obj)
        {
            UserInfo userInfo=(UserInfo)obj;

            Console.WriteLine();
            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + userInfo.Name);
            Console.WriteLine("I'm " + userInfo.Age + " years old this year");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

    public class UserInfo
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }

執行結果:

使用線程池創建的線程也能夠選擇傳遞參數或不傳遞參數,而且參數也能夠是值類型或引用類型(包括自定義類型)。看上面的結果發現了什麼?沒錯,第一次執行的方法的線程ID爲6,最後一次執行的方法的線程ID也爲6。這就說明第一次請求線程池的時候,線程池創建了一個線程,當它執行完成以後就以掛起狀態回到了線程池,在最後一次請求的時候,再次喚醒了該線程執行任務。這樣就很容易理解了。

在這裏我還發現了一個問題,就是,每次運行的時候,輸出的內容的順序都不必定是同樣的。(不僅是線程池,前面的也是)由於,我沒有給任何線程設置優先級(線程池不能設置線程的優先級),這裏其實就涉及到線程安全的問題了,很明顯如今這樣是非線程安全的。讓我舉個栗子形容一下的話,就像之前在學校下課了去吃飯同樣,蜂擁而上,毫無秩序。

線程安全就先不說,留在後面再說(包括前面所提到的線程同步的問題),這篇博客旨在理解多線程。由於我也沒有太深的理解。。。

上面咱們已經實現了無參數和有參數以及自定義參數多線程的實例,但是還有一個共同的問題那就是都沒有返回值。當咱們用多線程作實際開發的時候大部分都是會須要返回值的,那麼咱們可使用成員變量來試試。

   class Program
    {
        List<UserInfo> userInfoList = new List<UserInfo>();

        static void Main(string[] args)
        {
            Program program = new Program();

            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(program.Run);
            Thread thread = null;
            UserInfo userInfo = null;


            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                thread = new Thread(threadStart);
                thread.Start(userInfo);
                thread.Join();
            }

            foreach (UserInfo user in program.userInfoList)
            {
                Console.WriteLine("My name is " + user.Name);
                Console.WriteLine("I'm " + user.Age + " years old this year");
                Console.WriteLine("Thread ID is:" + user.ThreadId);
            }
            
            Console.ReadKey();
        }

        public void Run(object obj)
        {
            UserInfo userInfo = (UserInfo)obj;

            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
            userInfoList.Add(userInfo);
        }
    }

執行結果:

用上面這種方法勉強能夠知足返回值的需求,可是卻有很大的侷限性,由於這裏我使用的是成員變量,因此也就限制了線程調用的方法必須是在同一個類裏面。

因此也就有了下面的方法,使用委託異步調用的方法。

 

5、異步委託

委託的異步調用有兩個比較重要的方法:BeginInvoke()EndInvoke()

   class Program
    {
        //定義一個委託類
        private delegate UserInfo MyDelegate(UserInfo userInfo);


        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();
            List<UserInfo> userInfoList = new List<UserInfo>();
            UserInfo userInfo = null;
            UserInfo userInfoRes = null;

            //建立一個委託並綁定方法
            MyDelegate myDelegate = new MyDelegate(demoClass.Run);

            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                //傳入參數並執行異步委託
                IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);

                //異步操做是否完成
                while (!result.IsCompleted)
                {
                    Thread.Sleep(100);

                    Console.WriteLine("Main thread working...");
                    Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
                    Console.WriteLine();
                }

                //結束異步委託,並獲取返回值
                userInfoRes = myDelegate.EndInvoke(result);

                userInfoList.Add(userInfoRes);
            }

            foreach (UserInfo user in userInfoList)
            {
                Console.WriteLine("My name is " + user.Name);
                Console.WriteLine("I'm " + user.Age + " years old this year");
                Console.WriteLine("Thread ID is:" + user.ThreadId);
            }
            
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public UserInfo Run(UserInfo userInfo)
        {
            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
            Console.WriteLine();

            return userInfo;
        }
    }

執行結果:

BeginInvoke() 方法用於異步委託的執行開始,EndInvoke() 方法用於結束異步委託,並獲取異步委託執行完成後的返回值。IAsyncResult.IsCompleted 用於監視異步委託的執行狀態(true / false),這裏的時間是不定的,也就是說必定要等到異步委託執行完成以後,這個屬性纔會返回 true。若是異步委託的方法耗時較長,那麼主線程會一直工做下去。

BeginInvoke() 是能夠接受多個參數的,它的參數個數和參數類型取決於定義委託時的參數個數和類型,不管它有多少個參數,最後兩個參數都是不變的,下面咱們會說到。

 

那麼咱們還能夠用下面的方法 WaitOne(),自定義一個等待的時間,若是在這個等待時間內異步委託沒有執行完成,那麼就會執行 while 裏面的主線程的邏輯,反之就不會執行。

   class Program
    {
        //定義一個委託類
        private delegate UserInfo MyDelegate(UserInfo userInfo);


        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();
            List<UserInfo> userInfoList = new List<UserInfo>();
            UserInfo userInfo = null;
            UserInfo userInfoRes = null;

            //建立一個委託並綁定方法
            MyDelegate myDelegate = new MyDelegate(demoClass.Run);

            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                //傳入參數並執行異步委託
                IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);

                //阻止當前線程,直到 WaitHandle 收到信號,參數爲指定等待的毫秒數
                while (!result.AsyncWaitHandle.WaitOne(1000))
                {
                    Console.WriteLine("Main thread working...");
                    Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
                    Console.WriteLine();
                }

                //結束異步委託,並獲取返回值
                userInfoRes = myDelegate.EndInvoke(result);

                userInfoList.Add(userInfoRes);
            }

            foreach (UserInfo user in userInfoList)
            {
                Console.WriteLine("My name is " + user.Name);
                Console.WriteLine("I'm " + user.Age + " years old this year");
                Console.WriteLine("Thread ID is:" + user.ThreadId);
            }
            
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public UserInfo Run(UserInfo userInfo)
        {
            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
            Console.WriteLine();

            return userInfo;
        }
    }

執行結果:

WaitOne() 方法只能用於監視當前線程的對象,若是要監視多個對象可使用 WaitAny(WaitHandle[], int)WaitAll (WaitHandle[] , int) 這兩個方法。

   class Program
    {
        //定義一個委託類
        private delegate UserInfo MyDelegate(UserInfo userInfo);

        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();
            List<UserInfo> userInfoList = new List<UserInfo>();
            UserInfo userInfo = null;
            UserInfo userInfoRes = null;

            //建立一個委託並綁定方法
            MyDelegate myDelegate = new MyDelegate(demoClass.Run);

            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                //傳入參數並執行異步委託
                IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
                IAsyncResult result1 = myDelegate.BeginInvoke(userInfo, null, null);

                //定義要監視的對象,不能包含對同一對象的多個引用
                WaitHandle[] waitHandles = new WaitHandle[] { result.AsyncWaitHandle, result1.AsyncWaitHandle };
                while (!WaitHandle.WaitAll(waitHandles,1000))
                {
                    Console.WriteLine("Main thread working...");
                    Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
                    Console.WriteLine();
                }

                //結束異步委託,並獲取返回值
                userInfoRes = myDelegate.EndInvoke(result);
                userInfoList.Add(userInfoRes);

                userInfoRes = myDelegate.EndInvoke(result1);
                userInfoList.Add(userInfoRes);
            }

            foreach (UserInfo user in userInfoList)
            {
                Console.WriteLine("My name is " + user.Name);
                Console.WriteLine("I'm " + user.Age + " years old this year");
                Console.WriteLine("Thread ID is:" + user.ThreadId);
            }
            
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public UserInfo Run(UserInfo userInfo)
        {
            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
            Console.WriteLine();

            return userInfo;
        }
    }

執行結果:

WaitAll() 方法和 WaitAny() 方法均可以監視多個對象,不一樣的是 WaitAll() 方法須要等待全部的監視對象都收到信號以後纔會返回 true,不然返回 false。而 WaitAny() 則是當有一個監視對象收到信號以後就會返回一個 int 值,這個 int 值表明的是當前收到信號的監視對象的索引。注意:在定義監視對象的時候,不能包含對同一個對象的多個引用,我這裏是定義的兩個示例,因此是不一樣的對象。

接下來咱們看上面的執行結果。咦?爲何主線程沒有「工做」呢?

這裏你能夠在異步委託調用的 Run() 方法裏面爲線程設置一個幾秒鐘或者更長的掛起時間。而後在設置監視對象這裏設置一個小於線程掛起的時間,而後調試你就能發現問題的所在了。其實也不算是問題,只是以前的理解有誤。

其實 WaitAll() 方法和 WaitAny() 方法設置監視對象,而後指定一個時間(毫秒值),這裏的意思是當全部的監視對象在指定的時間內都接收到信號時(這裏是指 WaitAll() 方法),就不會執行 while 裏面的主線程的工做,反之就會執行。

這裏你可能會有疑問,若是是這樣,那我把前面的邏輯非運算符去掉那不就相反了麼。這麼理解邏輯上是沒錯的,可是我仍是要說的是,儘可能不要去嘗試,由於這會是個死循環。WaitAny() 這個方法也是同樣的理解,不一樣的是,它不須要等到全部的監視對象都收到信號,它只須要一個監視對象收到信號就夠了,這裏就不在演示了。

 

上面的方法能夠看出,咱們雖然使用的是異步的方式調用的方法,可是依舊須要等待異步的方法返回執行的結果,儘管咱們能夠不阻塞主線程,可是仍是以爲不太方便。因此也就有了本篇博客最後一個要點,異步委託的回調函數

   class Program
    {
        //定義一個委託類
        private delegate UserInfo MyDelegate(UserInfo userInfo);
static void Main(string[] args) { ThreadDemoClass demoClass = new ThreadDemoClass(); UserInfo userInfo = null; //建立一個委託並綁定方法 MyDelegate myDelegate = new MyDelegate(demoClass.Run); //建立一個回調函數的委託 AsyncCallback asyncCallback = new AsyncCallback(Complete); for (int i = 0; i < 3; i++) { userInfo = new UserInfo(); userInfo.Name = "Brambling" + i.ToString(); userInfo.Age = 33 + i; //傳入參數並執行異步委託,並設置回調函數 IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, null); } Console.WriteLine("Main thread working..."); Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString()); Console.WriteLine(); Console.ReadKey(); } public static void Complete(IAsyncResult result) { UserInfo userInfoRes = null; AsyncResult asyncResult = (AsyncResult)result; //獲取在其上調用異步調用的委託對象 MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate; //結束在其上調用的異步委託,並獲取返回值 userInfoRes = myDelegate.EndInvoke(result); Console.WriteLine("My name is " + userInfoRes.Name); Console.WriteLine("I'm " + userInfoRes.Age + " years old this year"); Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId); } } public class ThreadDemoClass { public UserInfo Run(UserInfo userInfo) { userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Child thread working..."); Console.WriteLine("Child thread ID is:" + userInfo.ThreadId); Console.WriteLine(); return userInfo; } }

執行結果:

從上面能夠看到主線程再執行了異步委託以後繼續執行了下去,而後在回調函數裏輸出了信息,也就是說在調用了異步委託以後就無論了,把以後的結束委託和獲取委託的返回值放到了回調函數中,由於回調函數是沒有返回值的,可是回調函數能夠有一個參數。上面說到的 BeginInvoke() 方法的最後兩個參數,它的倒數第二個參數就是一個回調函數的委託,最後一個參數能夠設置傳入回調函數的參數。以下:

   class Program
    {
        //定義一個委託類
        private delegate UserInfo MyDelegate(UserInfo userInfo);

        static List<UserInfo> userInfoList = new List<UserInfo>();

        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();
            UserInfo userInfo = null;

            //建立一個委託並綁定方法
            MyDelegate myDelegate = new MyDelegate(demoClass.Run);

            //建立一個回調函數的委託
            AsyncCallback asyncCallback = new AsyncCallback(Complete);

            //回調函數的參數
            string str = "I'm the parameter of the callback function!";

            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                //傳入參數並執行異步委託,並設置回調函數
                IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, str);
            }

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.WriteLine();
            
            Console.ReadKey();
        }

        public static void Complete(IAsyncResult result)
        {
            UserInfo userInfoRes = null;

            AsyncResult asyncResult = (AsyncResult)result;

            //獲取在其上調用異步調用的委託對象
            MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
            
            //結束在其上調用的異步委託,並獲取返回值
            userInfoRes = myDelegate.EndInvoke(result);

            Console.WriteLine("My name is " + userInfoRes.Name);
            Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
            Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);

            //獲取回調函數的參數
            string str = result.AsyncState as string;
            Console.WriteLine(str);
        }
    }

    public class ThreadDemoClass
    {
        public UserInfo Run(UserInfo userInfo)
        {
            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
            Console.WriteLine();

            return userInfo;
        }
    }

執行結果:

回調函數的參數也是 object 類型的,我這裏用的是一個 string 類型,可是它也能夠是自定義類型的參數。

本篇博客到此結束,在寫這篇博客的同時也讓我我的對多線程編程加深了理解,多線程編程的知識點還有不少,後面再繼續與你們分享。

 

參考:

http://www.cnblogs.com/leslies2/archive/2012/02/07/2310495.html#t1

相關文章
相關標籤/搜索