Thread,ThreadPool,Task, 到async await 的基本使用方法和理解


很久以前的一個面試場景:

面試官:說說你對JavaScript閉包的理解吧?

我:嗯,平時都是前端工程師在寫JS,我們一般只管寫后端代碼。

面試官:你是后端程序員啊,好吧,那問問你多線程編程的問題吧。

我:一般沒用到多線程。

面試官:............................. (面試結束)

 

 好了,哈哈一笑后,我們來看看 Thread,ThreadPool,Task, async, await 的基本使用方法。 

1.Thread

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");
            var thread = new Thread(new ThreadStart(ThreadTest));
            thread.Start();

            System.Console.WriteLine("主線程結束");
            System.Console.ReadLine();
        }

        private static void ThreadTest()
        {
            System.Console.WriteLine("開始執行子線程.... ");
            Thread.Sleep(100);
        }

執行結果:

 

上面的代碼,大家應該都很好理解,通過new Thread 來創建一個子線程,然后.Start() 開始執行。

我們F12 ThreadStart 看到 public delegate void ThreadStart();  是一個無參數無返回值的委托,那么,如果要在線程中執行一個有參數的方法怎么辦了?

OK,我們看Thread的構造函數

ParameterizedThreadStart 是什么?按字面上意思就是帶參數的ThreadStart,繼續F12看看它

果然是可以帶一個object的參數。

改造一下剛才的代碼:

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");
            var thread = new Thread(new ParameterizedThreadStart(ThreadTest));
            thread.Start(10);

            System.Console.WriteLine("主線程結束");
            System.Console.ReadLine();
        }

        private static void ThreadTest(object p)
        {
            System.Console.WriteLine("開始執行子線程.... 參數:{0} ", p);
            Thread.Sleep(100);
        } 

執行結果:

(當然還可以用ThreadStart(()=>{ }) 直接用lambda表達式的方式,這里就不寫示例代碼了 )  

 看到上面的執行結果,子線程因為Thread.Sleep(100) ,所以每次都最后才打印出輸出結果,那么你可能會疑問,如果我想等子線程執行完,我再執行主線程后面的代碼,怎么辦?

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");
            var thread = new Thread(new ParameterizedThreadStart(ThreadTest));
            thread.Start(10);
            thread.Join();

            System.Console.WriteLine("主線程結束");
            System.Console.ReadLine();
        }
private static void ThreadTest(object p) { System.Console.WriteLine("開始執行子線程.... 參數:{0} ", p); Thread.Sleep(100); }

注意看, 加了這句 thread.Join() ,管他什么意思,我們先看看執行結果吧!

OK,是不是明白Join()的意義了? 

2.ThreadPool

 為什么有了Thread還要出現ThreadPool了?

 如果你的代碼設計了大量使用Thread,那么有可能會超過系統最大的線程數導致崩潰,而且每次創建和銷毀線程也是很耗資源,ThreadPool就可以幫你提高代碼效率並管理你的線程。

 這不是重點,今天重點是學習它的基礎使用方法。 

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadTest), 1);

            System.Console.WriteLine("主線程結束");
            System.Console.ReadLine();
        }

        private static void ThreadTest(object p)
        {
            System.Console.WriteLine("開始執行子線程.... 參數:{0} ", p);
            Thread.Sleep(100);
        }

 先看看WaitCallback的定義

一個帶參數的委托,這就要求它的委托方法必須帶一個object的參數了。

ThreadPool靜態類通過QueueUserWorkItem()方法將工作函數排入線程池,它不需要我們主動的.Start(),那么他能不能Join()了?

我們點一下就知道了,它既不要你手動Start也沒有Join這樣的方法。 

好了,簡單學習Thread和ThreadPool后,發現他們構造函數中都是沒有返回值的委托,如果我們要在主線程中獲取子線程執行方法的返回值,怎么辦? Task閃亮登場了! 

3.Task

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");
            // new Task 創建方式-不帶參數
            //Task task = new Task(ThreadTest);
            //task.Start();

            // new Task 創建方式-帶參數
            //Task task=new Task(() =>  ThreadTest(10));

            //Task.Factory 創建方式-不帶參數
            //Task task = Task.Factory.StartNew(ThreadTest);
            //task.Start();

            //Task.Factory 創建方式-帶參數
            //Task task = Task.Factory.StartNew(() => ThreadTest(10));
            //task.Start();

            Task task = Task.Run(() => ThreadTest());
            //Task task = Task.Run(() => ThreadTest(10));

            System.Console.WriteLine("主線程結束");
            System.Console.ReadLine();
}
 

Task 的三種創建線程的方法,Task.Run() 不需要手動Start() 。其他兩種方式是需要手動Start()。 他們沒有Join()方法,取而代之的是Wait()

我們用Run()方法為例,看Task如何獲取方法的返回值。 

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");

            Task<int> task = Task.Run(() => ThreadTest(10));
            var result = task.Result;

            System.Console.WriteLine("主線程結束,result={0}", result);
            System.Console.ReadLine();
        }

        private static int ThreadTest(int i)
        {
            Thread.Sleep(100);
            System.Console.WriteLine("子線程開始");
            return i * 100;
        }

執行結果:

 通過task.Result 我們獲取到了在子線程中ThreadTest方法的返回值。有沒有注意,主線程是等子線程執行完之后才打印最后輸出的! task.Result 除了拿到返回值外,是不是和Wait()類似?

看到這里,你肯定會想到,這樣另起線程去跑耗時作業和我們平時普通寫法有什么區別?效率上會高很多嗎?我們來測試看看! 

常規方法: 

        private static void Main(string[] args)
        {
            DateTime dt1 = DateTime.Now;
            int count = 0;
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(10);
                count += i;
            }
            System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds);
            System.Console.ReadLine();
        }

 

Task方法:

        private static void Main(string[] args)
        {
            DateTime dt1 = DateTime.Now;
            Task<int> task = Task.Run(() =>
             {
                 int count = 0;
                 for (int i = 0; i < 10; i++)
                 {
                     Thread.Sleep(10);
                     count += i;
                 }
                 return count;
             });

            var result = task.Result;

            System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds);
            System.Console.ReadLine();
        }

這就很尷尬了,用Task反而執行時間更長!!!  是不是我的打開方式不對?  

4.async  await

async是修飾一個異步方法的關鍵字。有兩種返回類型(void 或者 Task<T>)

await必須是在async修飾的異步方法體內,await后面必須是一個異步方法或者Task。表示異步等待后面方法的結果。

1.返回void的使用方法

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");
            for (int i = 0; i < 10; i++)
            {
                ThreadTest(i);
            }
            System.Console.WriteLine("主線程執行完成");
            System.Console.ReadLine();
        }

        private static async void ThreadTest(int i)
        {
            await Task.Run(() =>
            {
                Thread.Sleep(10);
                System.Console.WriteLine("子線程開始,i=" + i);
            });
        }

執行結果

 2.返回Task<T>的使用方法 

        private static void Main(string[] args)
        {
            System.Console.WriteLine("主線程開始");
            var task = ThreadTest();
            System.Console.WriteLine("主線程執行完成,result="+ task.Result);
            System.Console.ReadLine();
        }
        private static async Task<int> ThreadTest()
        {
            var count = 0;
            await Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(10);
                    count += i;
                    System.Console.WriteLine("count="+ count);
                }
            });
            return count;
        }

返回的是Task<T>,那么像得到它的返回值,肯定也是通過.Result了,我們肯定有疑問了,這樣和直接寫Task有什么區別? 只是為了更加方便和美觀嗎?

接下來我們來測試下執行效率! 

        private static void Main(string[] args)
        {
            DateTime dt1 = DateTime.Now;
            var t = ThreadTest().Result;
            System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds + "  count=" + t);
            System.Console.ReadLine();
        }

        private static async Task<int> ThreadTest()
        {
            var count = 0;
            await Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(10);
                    count += i;
                }
            });
            return count;
        }

  

 執行效率和之前沒什么區別,不知道這種測試方式是否合理?跪求大神們分享賜教!

 今天就寫到這里,關於 async  await 和Task區別,async  await 線程阻塞問題,后面再來仔細研究。

 

 

 

========================================================================================================

分割線

========================================================================================================

昨天這篇博客發布后,收到大神的批評和指教,非常感謝!

讀了這篇文章后,才恍然大悟。 文章鏈接:https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx

 

分析下昨天測試“性能”的實例代碼的使用錯誤:

1.盲目使用task.Result來獲取最終結果,這樣導致主線程阻塞,都是等待子線程執行完畢。這樣的時間差比沒有太多意義。

2.都是在一個Task.Run()中模擬一個耗時操作,內部循環Thread.Sleep(10)。這樣把耗時操作搬到一個子線程去做,就算快也能快到哪里去了,完全沒有體現出多線程的優越性。

 

 下面是修改后的測試代碼,再看看async await給程序帶來的性能:

 1.普通Task,通過task.Result獲取返回值。

        private static void Main(string[] args)
        {
            DateTime dt1 = DateTime.Now;

            for (int i = 0; i <= 50; i++)
            {
                var re = Task.Run(() =>
                {
                    Thread.Sleep(10);
                    return i;
                }).Result;

                System.Console.WriteLine("result=" + re);

                if (i == 50)
                    System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds);
            }
            System.Console.ReadLine();
        }

  2.使用async await

        private static void Main(string[] args)
        {
            DateTime dt1 = DateTime.Now;

            for (int i = 0; i <= 50; i++)
            {
                var task = ThreadTest(dt1, i);
            }

            System.Console.ReadLine();
        }

        private static async Task<int> ThreadTest(DateTime dt1, int i)
        {
            int re = await Task.Run(() =>
            {
                Thread.Sleep(10);
                return i;
            });

            System.Console.WriteLine("result=" + re);

            if (i == 50)
                System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds);

            return re;
        }

 

 async await 真正體現了它的性能所在 。

總結:

1.不要盲目使用task.Result

2.async await的意義(或者說和Task的區別)在於不阻塞線程的情況下獲取返回值。

 

本文博客園地址:http://www.cnblogs.com/struggle999/p/6933376.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM