欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

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

admin
2024年1月3日 9:9 本文熱度 993

說起異步,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("主線程開始");

//返回值類型為string

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

 

出處:http://www.hzhcontrols.com/
原文:http://www.hzhcontrols.com/new-605059.html
本文版權歸原作者所有
歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利


該文章在 2024/1/3 23:59:27 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved