C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!

來源:互聯網
上載者:User

標籤:html   lease   字串類   多線程   興趣   stopwatch   actor   app   cto   

轉自:https://www.cnblogs.com/doforfuture/p/6293926.html

說起非同步,Thread,Task,async/await,IAsyncResult 這些東西肯定是繞不開的,今天就來依次聊聊他們

1.線程(Thread)

多線程的意義在於一個應用程式中,有多個執行部分可以同時執行;對於比較耗時的操作(例如io,資料庫操作),或者等待響應(如WCF通訊)的操作,可以單獨開啟後台線程來執行,這樣主線程就不會阻塞,可以繼續往下執行;等到後台線程執行完畢,再通知主線程,然後做出對應操作!

在C#中開啟新線程比較簡單

static void Main(string[] args){    Console.WriteLine("主線程開始");    //IsBackground=true,將其設定為後台線程    Thread t = new Thread(Run) { IsBackground = true };    t.Start();
   Console.WriteLine("主線程在做其他的事!"); //主線程結束,後台線程會自動結束,不管有沒有執行完成 //Thread.Sleep(300); Thread.Sleep(1500); Console.WriteLine("主線程結束");}static void Run(){ Thread.Sleep(700); Console.WriteLine("這是後台線程調用");}

 執行結果如,

可以看到在啟動後台線程之後,主線程繼續往下執行了,並沒有等到後台線程執行完之後。

1.1 線程池

試想一下,如果有大量的任務需要處理,例如網站後台對於HTTP請求的處理,那是不是要對每一個請求建立一個後台線程呢?顯然不合適,這會佔用大量記憶體,而且頻繁地建立的過程也會嚴重影響速度,那怎麼辦呢?線程池就是為瞭解決這一問題,把建立的線程存起來,形成一個線程池(裡面有多個線程),當要處理任務時,若線程池中有空閑線程(前一個任務執行完成後,線程不會被回收,會被設定為空白閑狀態),則直接調用線程池中的線程執行(例asp.net處理機制中的Application對象),

使用案例:

for (int i = 0; i < 10; i++){    ThreadPool.QueueUserWorkItem(m =>    {        Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());    });}Console.Read();

運行結果:

可以看到,雖然執行了10次,但並沒有建立10個線程。

 1.2 訊號量(Semaphore)

 Semaphore負責協調線程,可以限制對某一資源訪問的線程數量

 這裡對SemaphoreSlim類的用法做一個簡單的案例:

static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多隻能有三個線程同時訪問static void Main(string[] args){    for (int i = 0; i < 10; i++)    {        new Thread(SemaphoreTest).Start();    }    Console.Read();}static void SemaphoreTest(){    semLim.Wait();    Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "開始執行");    Thread.Sleep(2000);    Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "執行完畢");    semLim.Release();}

執行結果如下:

可以看到,剛開始只有三個線程在執行,當一個線程執行完畢並釋放之後,才會有新的線程來執行方法!

除了SemaphoreSlim類,還可以使用Semaphore類,感覺更加靈活,感興趣的話可以搜一下,這裡就不做示範了!

2.Task

Task是.NET4.0加入的,跟線程池ThreadPool的功能類似,用Task開啟新任務時,會從線程池中調用線程,而Thread每次執行個體化都會建立一個新的線程。

Console.WriteLine("主線程啟動");//Task.Run啟動一個線程//Task啟動的是後台線程,要在主線程中等待後台線程執行完畢,可以調用Wait方法//Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task啟動"); });Task task = Task.Run(() => {     Thread.Sleep(1500);    Console.WriteLine("task啟動");});Thread.Sleep(300);task.Wait();Console.WriteLine("主線程結束");

執行結果如下:

開啟新任務的方法:Task.Run()或者Task.Factory.StartNew(),開啟的是後台線程

要在主線程中等待後台線程執行完畢,可以使用Wait方法(會以同步的方式來執行)。不用Wait則會以非同步方式來執行。

比較一下Task和Thread:

static void Main(string[] args){    for (int i = 0; i < 5; i++)    {        new Thread(Run1).Start();    }    for (int i = 0; i < 5; i++)    {        Task.Run(() => { Run2(); });    }}static void Run1(){    Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId);}static void Run2(){    Console.WriteLine("Task調用的Thread Id =" + Thread.CurrentThread.ManagedThreadId);}

執行結果:

可以看出來,直接用Thread會開啟5個線程,用Task(用了線程池)開啟了3個!

2.1 Task<TResult>

Task<TResult>就是有傳回值的Task,TResult就是傳回值類型。

Console.WriteLine("主線程開始");//傳回值類型為stringTask<string> task = Task<string>.Run(() => {    Thread.Sleep(2000);     return Thread.CurrentThread.ManagedThreadId.ToString(); });//會等到task執行完畢才會輸出;Console.WriteLine(task.Result);Console.WriteLine("主線程結束");

運行結果:

通過task.Result可以取到傳回值,若取值的時候,後台線程還沒執行完,則會等待其執行完畢!

簡單提一下:

Task任務可以通過CancellationTokenSource類來取消,感覺用得不多,用法比較簡單,感興趣的話可以搜一下!

 3. async/await

async/await是C#5.0中推出的,先上用法:

static void Main(string[] args){    Console.WriteLine("-------主線程啟動-------");    Task<int> task = GetStrLengthAsync();    Console.WriteLine("主線程繼續執行");    Console.WriteLine("Task返回的值" + task.Result);    Console.WriteLine("-------主線程結束-------");}static async Task<int> GetStrLengthAsync(){    Console.WriteLine("GetStrLengthAsync方法開始執行");    //此處返回的<string>中的字串類型,而不是Task<string>    string str = await GetString();    Console.WriteLine("GetStrLengthAsync方法執行結束");    return str.Length;}static Task<string> GetString(){
   //Console.WriteLine("GetString方法開始執行") return Task<string>.Run(() => { Thread.Sleep(2000); return "GetString的傳回值"; });}

async用來修飾方法,表明這個方法是非同步,聲明的方法的傳回型別必須為:void,Task或Task<TResult>。

await必須用來修飾Task或Task<TResult>,而且只能出現在已經用async關鍵字修飾的非同步方法呼叫中。通常情況下,async/await成對出現才有意義,

看看運行結果:

可以看出來,main函數調用GetStrLengthAsync方法後,在await之前,都是同步執行的,直到遇到await關鍵字,main函數才返回繼續執行。

那麼是否是在遇到await關鍵字的時候程式自動開啟了一個後台線程去執行GetString方法呢?

現在把GetString方法中的那行注釋加上,啟動並執行結果是:

大家可以看到,在遇到await關鍵字後,沒有繼續執行GetStrLengthAsync方法後面的操作,也沒有馬上反回到main函數中,而是執行了GetString的第一行,以此可以判斷await這裡並沒有開啟新的線程去執行GetString方法,而是以同步的方式讓GetString方法執行,等到執行到GetString方法中的Task<string>.Run()的時候才由Task開啟了後台線程!

那麼await的作用是什麼呢?

可以從字面上理解,上面提到task.wait可以讓主線程等待後台線程執行完畢,await和wait類似,同樣是等待,等待Task<string>.Run()開始的後台線程執行完畢,不同的是await不會阻塞主線程,只會讓GetStrLengthAsync方法暫停執行。

那麼await是怎麼做到的呢?有沒有開啟新線程去等待?

只有兩個線程(主線程和Task開啟的線程)!至於怎麼做到的(我也不知道......>_<),大家有興趣的話研究下吧!

4.IAsyncResult

IAsyncResult自.NET1.1起就有了,包含可非同步作業的方法的類需要實現它,Task類就實現了該介面

在不藉助於Task的情況下怎麼實現非同步呢?

class Program{    static void Main(string[] args)    {        Console.WriteLine("主程式開始--------------------");        int threadId;        AsyncDemo ad = new AsyncDemo();        AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);        IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null);        Thread.Sleep(0);        Console.WriteLine("主線程線程 {0} 正在運行.",Thread.CurrentThread.ManagedThreadId)        //會阻塞線程,直到後台線程執行完畢之後,才會往下執行        result.AsyncWaitHandle.WaitOne();        Console.WriteLine("主程式在做一些事情!!!");        //擷取非同步執行的結果        string returnValue = caller.EndInvoke(out threadId, result);        //釋放資源        result.AsyncWaitHandle.Close();        Console.WriteLine("主程式結束--------------------");        Console.Read();    }}public class AsyncDemo{    //供後台線程執行的方法    public string TestMethod(int callDuration, out int threadId)    {        Console.WriteLine("測試方法開始執行.");        Thread.Sleep(callDuration);        threadId = Thread.CurrentThread.ManagedThreadId;        return String.Format("測試方法執行的時間 {0}.", callDuration.ToString());    }}public delegate string AsyncMethodCaller(int callDuration, out int threadId);

關鍵步驟就是紅色字型的部分,運行結果:

和Task的用法差異不是很大!result.AsyncWaitHandle.WaitOne()就類似Task的Wait。

 5.Parallel

最後說一下在迴圈中開啟多線程的簡單方法:

Stopwatch watch1 = new Stopwatch();watch1.Start();for (int i = 1; i <= 10; i++){    Console.Write(i + ",");    Thread.Sleep(1000);}watch1.Stop();Console.WriteLine(watch1.Elapsed);Stopwatch watch2 = new Stopwatch();watch2.Start();//會調用線程池中的線程Parallel.For(1, 11, i =>{    Console.WriteLine(i + ",線程ID:" + Thread.CurrentThread.ManagedThreadId);    Thread.Sleep(1000);});watch2.Stop();Console.WriteLine(watch2.Elapsed);

運行結果:

迴圈List<T>:

List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 };
Parallel.ForEach<int>(list, n =>{ Console.WriteLine(n); Thread.Sleep(1000);});

執行Action[]數組裡面的方法:

Action[] actions = new Action[] {    new Action(()=>{       Console.WriteLine("方法1");   }),    new Action(()=>{       Console.WriteLine("方法2");   })};Parallel.Invoke(actions);
6.非同步回調

文中所有Task<TResult>的傳回值都是直接用task.result擷取,這樣如果背景工作沒有執行完畢的話,主線程會等待其執行完畢,這樣的話就和同步一樣了(看上去一樣,但其實await的時候並不會造成線程的阻塞,web程式感覺不到,但是wpf,winform這樣的傳統型程式若不使用非同步,會造成UI線程的阻塞)。簡單示範一下Task回呼函數的使用:

Console.WriteLine("主線程開始");Task<string> task = Task<string>.Run(() => {    Thread.Sleep(2000);     return Thread.CurrentThread.ManagedThreadId.ToString(); });//會等到任務執行完之後執行task.GetAwaiter().OnCompleted(() =>{    Console.WriteLine(task.Result);});Console.WriteLine("主線程結束");Console.Read();

執行結果:

OnCompleted中的代碼會在任務執行完成之後執行!

另外task.ContinueWith()也是一個重要的方法:

Console.WriteLine("主線程開始");Task<string> task = Task<string>.Run(() => {    Thread.Sleep(2000);     return Thread.CurrentThread.ManagedThreadId.ToString(); });task.GetAwaiter().OnCompleted(() =>{    Console.WriteLine(task.Result);});task.ContinueWith(m=>{Console.WriteLine("第一個任務結束啦!我是第二個任務");});Console.WriteLine("主線程結束");Console.Read();

執行結果:

ContinueWith()方法可以讓該後台線程繼續執行新的任務。

Task的使用還是比較靈活的,大家可以研究下,好了,以上就是全部內容了,篇幅和能力都有限,希望對大家有用!

C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.