C#使用Task實(shí)現(xiàn)異步方法
一、async和await特性的結(jié)構(gòu)
1. 異步和同步
同步方法:如果一個方法被調(diào)用了,等待其執(zhí)行所有處理后調(diào)用方法才繼續(xù)執(zhí)行的方法。
異步方法:異步方法能在處理完成之前就回到調(diào)用方法。
2.async和await
調(diào)用方法:該方法調(diào)用異步方法,然后在異步方法(可能在相同的線程,也可能在不同的線程)執(zhí)行其任務(wù)時繼續(xù)執(zhí)行。
異步(async)方法:該方法異步執(zhí)行其工作,被調(diào)用時立即回到調(diào)用方法。
await表達(dá)式:用于異步方法內(nèi)部,指明需要異步執(zhí)行的任務(wù)。一個異步方法需要至少包含一個await表達(dá)式。

二、什么是異步方法
1.異步方法的結(jié)構(gòu)
異步方法在完成其工作之前即返回到調(diào)用方法,然后在調(diào)用方法繼續(xù)執(zhí)行的時候完成其工作。
- 方法頭包含async方法修飾符,該修飾符僅標(biāo)識此方法是一個異步方法。
- 包含至少一個await表達(dá)式,表示可以異步完成的任務(wù)。
- 必須具備以下三種返回類型中的一種:void、Task、Task<T>。
- 異步方法的參數(shù)可以是任意類型,但不能為ref或out。
- 按照約定,異步方法的名稱應(yīng)該為Async為后綴。
- 除了方法以外,Lambda表達(dá)式和匿名方法也可以作為異步對象。

2.異步方法三種返回類型理解
1. Task<T> :如果調(diào)用方法要從調(diào)用中獲取一個T類型的值,異步方法的返回類型就必須是Task<T>。調(diào)用方法通過讀取Task的Result屬性來獲取這個T類型的值。
// 使用返回Task<int>對象的異步方法代碼示例:
static class DoAsyncStuff
{
public static async Task<int> CalculateSumAsync(int i1, int i2)
{
int sum = await Task.Run(() => GetSum(i1, i2));
return sum;//注意返回的是一個int類型
}
private static int GetSum(int i1, int i2) { return i1 + i2; }
}
internal class Program
{
static void Main(string[] args)
{
Task<int> task = DoAsyncStuff.CalculateSumAsync(1, 2);
//處理其他事情...
Console.WriteLine("Value:{0}",task.Result);//調(diào)用方法通過Result獲取這個int類型的值
}
}2.Task : 如果調(diào)用方法不需要從異步方法中返回某個值,但需要檢查異步方法的狀態(tài),那么異步方法可以返回一個Task類型的對象,這時即使異步方法中出現(xiàn)了return語句,也不會返回任何東西。
//使用Task不返回類型的異步方法代碼示例:
static class DoAsyncStuff
{
public static async Task CalculateSumAsync(int i1, int i2)
{
int sum = await Task.Run(() => GetSum(i1, i2));
Console.WriteLine("Value:{0}",sum);
}
private static int GetSum(int i1, int i2) { return i1 + i2; }
}
internal class Program
{
static void Main(string[] args)
{
Task task = DoAsyncStuff.CalculateSumAsync(1, 2);
//處理其他事情...
task.Wait();
Console.WriteLine("異步方法結(jié)束");
}
}3. void :如果調(diào)用方法僅僅想執(zhí)行異步方法,而不需要與它進(jìn)行進(jìn)一步交互時【稱為“調(diào)用并忘記”】,異步方法可以返回void類型。
//使用“調(diào)用并忘記”的異步方法的代碼示例:
static class DoAsyncStuff
{
public static async void CalculateSumAsync(int i1, int i2)
{
int sum = await Task.Run(() => GetSum(i1, i2));
Console.WriteLine("Value:{0}",sum);
}
private static int GetSum(int i1, int i2) { return i1 + i2; }
}
internal class Program
{
static void Main(string[] args)
{
DoAsyncStuff.CalculateSumAsync(1, 2);
//處理其他事情...
Thread.Sleep(200);
Console.WriteLine("Program Exiting");
}
}3.異步方法的控制流
1. 異步方法的結(jié)構(gòu)包含三個不同的區(qū)域:

控制流闡述:
- 調(diào)用異步方法后,知道遇到第一個await表達(dá)式之前都是同步的。
- 遇到await表達(dá)式之后,異步方法將控制返回到調(diào)用方法。如果方法的返回類型為Task或Task<T>類型,將創(chuàng)建一個Task對象,表示需異步完成的任務(wù)和后續(xù),然后將Task返回到調(diào)用方法。
- 到目前為止,已經(jīng)出現(xiàn)兩個控制流,異步方法內(nèi)的和調(diào)用方法內(nèi)的。異步方法的代碼完成以下工作:
- 異步執(zhí)行await表達(dá)式的空閑任務(wù)。
- 當(dāng)await表達(dá)式完成時,執(zhí)行后續(xù)部分。如果后續(xù)還有await表達(dá)式,將重復(fù)此步驟:異步執(zhí)行await表達(dá)式,然后執(zhí)行后續(xù)部分。
- 當(dāng)后續(xù)部分遇到return語句或到達(dá)方法末尾時:
- 如果方法返回類型為void,控制流將退出。
- 如果方法返回類型為Task,后續(xù)部分設(shè)置Task的屬性并退出。
- 如果方法返回類型為Task<T>,后續(xù)部分還將設(shè)置Task的Result屬性。
- 同時調(diào)用方法中的代碼繼續(xù)其進(jìn)程,從異步方法獲取Task對象。當(dāng)需要其實(shí)際值時,就引用Task對象的Result屬性,屆時,如果異步方法設(shè)置了該屬性,調(diào)用方法就能調(diào)用其值并繼續(xù),否則將暫停等待該屬性被設(shè)置,然后再繼續(xù)執(zhí)行。
需要注意的是:異步方法的return語句并沒有真正返回一個值,它只是退出了,異步方法中的返回類型始終是方法頭中聲明的返回類型。

三、await表達(dá)式
await表達(dá)式指定了一個異步執(zhí)行的任務(wù)。這個任務(wù)可能是一個Task類型的對象,也可能不是,默認(rèn)情況下這個任務(wù)在當(dāng)前線程異步運(yùn)行。

我們可能需要編寫自己的方法作為await表達(dá)式的任務(wù)。最簡單的方式是在你的方法中使用Task.Run創(chuàng)建一個Task。(關(guān)于Task.Run即在不同的線程上運(yùn)行你的方法)Task.Run方法有8個重載 ,如下圖所示:

以Func<int> 委托作為參數(shù)的代碼示例:
class MyClass
{
public int Get10()
{
return 10;
}
public async Task DoWorkAsync()
{
//方式1:使用Get10創(chuàng)建名為ten的Func<int>委托,將該委托傳入Task.Run方法
Func<int> ten = new Func<int>(Get10);
int a = await Task.Run(ten);
//方式2:直接在Task.Run中創(chuàng)建委托,傳入Get10方法
int b = await Task.Run(new Func<int>(Get10));
//方式3:使用與Func<T>兼容的Lambda表達(dá)式,Lambda表達(dá)式將隱式轉(zhuǎn)換為該委托
int c = await Task.Run(() => { return 10; });
Console.WriteLine("{0},{1},{2}",a,b,c);
}
}
internal class Program
{
static void Main(string[] args)
{
Task task = new MyClass().DoWorkAsync();
task.Wait();
}
}從Task.Run的重載中可以發(fā)現(xiàn),可以作為Run中第一個參數(shù)的委托有四種:

使用四種委托作為參數(shù)的Run方法代碼示例:
class MyClass
{
public static async Task DoWorkAsync()
{
await Task.Run(() => Console.WriteLine(5.ToString()));//Aciton,無參無返
Console.WriteLine((await Task.Run(() => 6)).ToString());//TResult Func(),無參有返,返回TResult類型對象
await Task.Run(() => Task.Run(() => Console.WriteLine(7.ToString())));//Task Func(),無參有返,返回簡單Task對象
Console.WriteLine((await Task.Run(() => Task.Run(() => 8))).ToString());//Task<TResult> Func(),無參有返,返回Task<T>對象
}
}
internal class Program
{
static void Main(string[] args)
{
Task task = MyClass.DoWorkAsync();
task.Wait();
}
}假如我們需要一個接受參數(shù)的方法,但是無法匹配上述的四種委托(接受沒有參數(shù)的方法的委托),我們可以創(chuàng)建一個Lambda表達(dá)式,其唯一行為就是運(yùn)行我們的接受參數(shù)的方法,來滿足Run方法所能接受的委托形式。代碼示例如下:
class MyClass
{
//需要使用異步的方法
public static int GetSum(int a, int b) { return a + b; }
public static async Task DoWorkAsync(int a,int b)
{
int result = await Task.Run(() => GetSum(a, b));//創(chuàng)建一個滿足Func<TResult>委托形式的Lambda表達(dá)式
Console.WriteLine(result);
}
}
internal class Program
{
static void Main(string[] args)
{
Task task = MyClass.DoWorkAsync(6,7);
task.Wait();
}
}四、取消一個異步操作
一些.NET異步方法允許你請求終止執(zhí)行,我們可以在自己的異步方法中加入這個特性。有兩個類:CancellationToken和CancellationTokenSource是專門為了取消異步操作設(shè)計(jì)的。
1. CancellationToken對象包含了一個任務(wù)是否應(yīng)被取消的消息。擁有CancellationToken對象的任務(wù)需要定期檢查其令牌(token)的狀態(tài),如果CancellationToken對象中的IsCancellationRequested屬性為true,任務(wù)需停止其操作并返回。CancellationToken是不可逆的,也就是一旦IsCancellationRequested設(shè)置為true就不能更改了。

2. CancellationTokenSource對象創(chuàng)建可分配給不同任務(wù)的CancellationToken對象。任何持有CancellationTokenSource的對象都可以調(diào)用其Cancel方法,這會將CancellationToken對象中的IsCancellationRequested設(shè)置為true。
3. 使用這兩個取消類的代碼:
class MyClass
{
public async Task RunAsync(CancellationToken ct)
{
if (ct.IsCancellationRequested)
return;
await Task.Run(()=>CycleMethod(ct));
}
void CycleMethod(CancellationToken ct)
{
Console.WriteLine("開始CycleMethod方法");
const int max = 5;
for (int i = 0; i < max; i++)
{
if (ct.IsCancellationRequested)
return;
Thread.Sleep(1000);
Console.WriteLine("完成:{0}/{1}",i+1,max);
}
}
}
internal class Program
{
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
MyClass mc = new MyClass();
Task t = mc.RunAsync(ct);
/*
取消異步操作的過程是協(xié)同的:
即調(diào)用CancellationTokenSource的Cancel時,它本身并不會執(zhí)行取消操作
而是會將CancellationToken的IsCancellationRequested屬性設(shè)置為true
包含CancellationToken的代碼負(fù)責(zé)檢查該屬性,并判斷是否需要停止執(zhí)行并返回
*/
//Thread.Sleep(3000);
//cts.Cancel();//解除注釋將產(chǎn)生取消異步的操作
t.Wait();
Console.WriteLine("是否取消了異步方法:{0}", ct.IsCancellationRequested);
}
}五、異常處理的await表達(dá)式
就使用try...catch...正常處理異常就行。
class MyClass
{
public static async Task BadAsync()
{
try
{
await Task.Run(() => { throw new Exception(); });
}catch (Exception ex)
{
Console.WriteLine("異步中拋出異常...");
}
}
}
internal class Program
{
static void Main(string[] args)
{
Task task = MyClass.BadAsync();
task.Wait();
Console.WriteLine("Task Status:{0}",task.Status);//Task Status:RanToCompletion,因?yàn)門ask沒有被取消
Console.WriteLine("Task IsFaulted:{0}",task.IsFaulted);//Task IsFaulted:False,因?yàn)闆]有未處理的異常
}
}六、在調(diào)用方法中同步地等待任務(wù)
1. Wait
Task的Wait方法可以讓調(diào)用方法等待異步方法完成,在Wait方法處,流程是阻塞的。
class MyClass
{
public void PrintA()
{
const int max = 10;
for (int i = 0; i < max; i++)
{
Thread.Sleep(1000);
Console.WriteLine("A任務(wù)完成:{0}/{1}", i + 1, max);
}
}
public async Task ForWaitAsync()
{
await Task.Run(new Action(PrintA));
}
}
internal class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
Task t = mc.ForWaitAsync();
Thread.Sleep(6000);//主線程休眠6秒
//等待任務(wù)完成,使用此方法可以讓主方法等待異步方法完成,否則主方法休眠六秒結(jié)束時異步方法也將直接結(jié)束
t.Wait();
}
}2. WaitAll和WaitAny
Wait是等待單一任務(wù)完成,我們也可以使用WaitAll和WaitAny等待多個任務(wù)。(這兩個方法是同步方法,知道等待條件滿足后再繼續(xù)執(zhí)行,不然流程會阻塞在那里)
class MyClass
{
public void PrintA()
{
const int max = 10;
for (int i = 0; i < max; i++)
{
Thread.Sleep(1000);
Console.WriteLine("A任務(wù)完成:{0}/{1}", i + 1, max);
}
Console.WriteLine("A任務(wù)全部完成!");
}
public void PrintB()
{
const int max = 5;
for (int i = 0; i < max; i++)
{
Thread.Sleep(1000);
Console.WriteLine("B任務(wù)完成:{0}/{1}", i + 1, max);
}
Console.WriteLine("B任務(wù)全部完成!");
}
public async Task AForWaitAsync()
{
await Task.Run(new Action(PrintA));
}
public async Task BForWaitAsync()
{
await Task.Run(new Action(PrintB));
}
}
internal class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
Task a = mc.AForWaitAsync();
Task b = mc.BForWaitAsync();
Task[] task = new Task[] { a, b };//存放Task的數(shù)組
Thread.Sleep(8000);//調(diào)用方法休眠8秒
//Task.WaitAll(task);//等待所有任務(wù)完成
Task.WaitAny(task);//等待任意一個任務(wù)完成后將不再阻塞
}
}WaitAll和WaitAny分別還包含四個重載:

七、在異步方法中異步地等待任務(wù)
有時在異步方法中,你會希望用await表達(dá)式來等待Task。這時異步方法會返回到調(diào)用方法,但該異步方法會等待一個或所有任務(wù)完成??梢酝ㄟ^Task.WhenAll或Task.WhenAny方法來實(shí)現(xiàn)。這兩個方法稱為組合子。
class MyClass
{
public void PrintA()
{
const int max = 10;
for (int i = 0; i < max; i++)
{
Thread.Sleep(1000);
Console.WriteLine("A任務(wù)完成:{0}/{1}", i + 1, max);
}
}
public void PrintB()
{
const int max = 5;
for (int i = 0; i < max; i++)
{
Thread.Sleep(1000);
Console.WriteLine("B任務(wù)完成:{0}/{1}", i + 1, max);
}
}
public async Task AAsync()
{
await Task.Run(new Action(PrintA));
}
public async Task BAsync()
{
await Task.Run(new Action(PrintB));
}
public async Task ForWhenAsync()
{
Task a = AAsync();
Task b = BAsync();
Task[] tasks = { a, b };//Task數(shù)組
//await Task.WhenAll(tasks);//在異步中等待所有任務(wù)
await Task.WhenAny(tasks);//在異步中等待任意一個任務(wù)
//此異步方法中的流程會阻塞在Task.WhenAny(tasks)中,直到某個條件滿足才會到達(dá)這里
Console.WriteLine("現(xiàn)在任務(wù)A是否完成:{0}",a.IsCompleted?"Yes":"No");
Console.WriteLine("現(xiàn)在任務(wù)B是否完成:{0}",b.IsCompleted?"Yes":"No");
}
}
internal class Program
{
static void Main(string[] args)
{
const int max = 12;
MyClass mc = new MyClass();
mc.ForWhenAsync();
for (int i = 0;i < max ; i++)
{
Thread.Sleep(1000);
Console.WriteLine("Main任務(wù)完成:{0}/{1}", i + 1, max);
}
}
}八、Task.Delay方法
Task.Delay方法創(chuàng)建一個對象,該對象將暫停其在線程中的處理,并在一定時間之后完成。和Thread.Sleep阻塞線程不同的是,Task.Delay方法不會阻塞線程,線程可以繼續(xù)處理其他工作。
class Simple
{
Stopwatch sw = new Stopwatch();
public void DoRun()
{
Console.WriteLine("調(diào)用之前");
ShowDelayAsync();
Console.WriteLine("調(diào)用之后");
}
private async void ShowDelayAsync()
{
sw.Start();
Console.WriteLine("Delay 執(zhí)行之前:{0}",sw.ElapsedMilliseconds);
await Task.Delay(1000);//創(chuàng)建了一個Task對象,該對象將暫停其在線程中的處理,所以在打印下一句話之前,控制回到了調(diào)用方法打印調(diào)用方法中的"調(diào)用之后"
Console.WriteLine("Delay 執(zhí)行之后:{0}", sw.ElapsedMilliseconds);
}
}
internal class Program
{
static void Main(string[] args)
{
Simple simple = new Simple();
simple.DoRun();
Console.Read();
}
}到此這篇關(guān)于C#使用Task實(shí)現(xiàn)異步方法的文章就介紹到這了,更多相關(guān)C# Task異步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用struct直接轉(zhuǎn)換下位機(jī)數(shù)據(jù)的示例代碼
這篇文章主要介紹了C#使用struct直接轉(zhuǎn)換下位機(jī)數(shù)據(jù)的示例代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
C#實(shí)現(xiàn)窗體間傳遞數(shù)據(jù)實(shí)例
這篇文章主要介紹了C#實(shí)現(xiàn)窗體間傳遞數(shù)據(jù)實(shí)例,需要的朋友可以參考下2014-07-07
C#中nameof的實(shí)現(xiàn)實(shí)例
本文主要介紹了C#中nameof的實(shí)現(xiàn)實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-11-11
unity實(shí)現(xiàn)手機(jī)端搖桿控制人物移動
這篇文章主要為大家詳細(xì)介紹了unity實(shí)現(xiàn)手機(jī)端搖桿控制人物移動,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
C#利用OLEDB實(shí)現(xiàn)將DataTable寫入Excel文件中
這篇文章主要為大家詳細(xì)介紹了C#如何利用OLEDB實(shí)現(xiàn)將DataTable寫入Excel文件中,文中的示例代碼簡潔易懂,具有一定的借鑒價(jià)值,需要的可以參考一下2023-02-02
Unity3D實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲(1)
這篇文章主要為大家詳細(xì)介紹了Unity3D實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
如何在C#中使用 CancellationToken 處理異步任務(wù)
這篇文章主要介紹了如何在C#中使用 CancellationToken 處理異步任務(wù),幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03

