C#異步編程由淺入深:Async/Await的作用
考慮到直接講實(shí)現(xiàn)一個(gè)類Task庫(kù)思維有點(diǎn)跳躍,所以本節(jié)主要講解Async/Await的本質(zhì)作用(解決了什么問題),以及Async/Await的工作原理。實(shí)現(xiàn)一個(gè)類Task的庫(kù)則放在后面講。
class Program
{
public static string GetMessage()
{
return Console.ReadLine();
}
public static string TranslateMessage(string msg)
{
return msg;
}
public static void DispatherMessage(string msg)
{
switch (msg)
{
case "MOUSE_MOVE":
{
OnMOUSE_MOVE(msg);
break;
}
case "MOUSE_DOWN":
{
OnMouse_DOWN(msg);
break;
}
default:
break;
}
}
public static void OnMOUSE_MOVE(string msg)
{
Console.WriteLine("開始繪制鼠標(biāo)形狀");
}
public static int Http()
{
Thread.Sleep(1000);//模擬網(wǎng)絡(luò)IO延時(shí)
return 1;
}
public static void HttpAsync(Action<int> action,Action error)
{
//這里我們用另一個(gè)線程來實(shí)現(xiàn)異步IO,由于Http方法內(nèi)部是通過Sleep來模擬網(wǎng)絡(luò)IO延時(shí)的,這里也只能通過另一個(gè)線程來實(shí)現(xiàn)異步IO
//但記住,多線程是實(shí)現(xiàn)異步IO的一個(gè)手段而已,它不是必須的,后面會(huì)講到如何通過一個(gè)線程來實(shí)現(xiàn)異步IO。
Thread thread = new Thread(() =>
{
try
{
int res = Http();
action(res);
}
catch
{
error();
}
});
thread.Start();
}
public static Task<int> HttpAsync()
{
return Task.Run(() =>
{
return Http();
});
}
public static void OnMouse_DOWN(string msg)
{
HttpAsync()
.ContinueWith(t =>
{
if(t.Status == TaskStatus.Faulted)
{
}else if(t.Status == TaskStatus.RanToCompletion)
{
Console.WriteLine(1);
//做一些工作
}
})
.ContinueWith(t =>
{
if (t.Status == TaskStatus.Faulted)
{
}
else if (t.Status == TaskStatus.RanToCompletion)
{
Console.WriteLine(2);
//做一些工作
}
})
.ContinueWith(t =>
{
if (t.Status == TaskStatus.Faulted)
{
}
else if (t.Status == TaskStatus.RanToCompletion)
{
Console.WriteLine(3);
//做一些工作
}
});
}
static void Main(string[] args)
{
while (true)
{
string msg = GetMessage();
if (msg == "quit") return;
string m = TranslateMessage(msg);
DispatherMessage(m);
}
}
}
在OnMouse_DOWN這個(gè)處理函數(shù)中,我們使用Task的ContinueWith函數(shù)進(jìn)行鏈?zhǔn)讲僮鳎鉀Q了回調(diào)地獄問題,但是總感覺有點(diǎn)那么不爽,我們假想有個(gè)關(guān)鍵字await它能實(shí)現(xiàn)以下作用:首先await必須是Task類型,必須是Task類型的(其實(shí)不是必要條件,后面會(huì)講到)原因是保證必須有ContinueWith這個(gè)函數(shù),如果Task沒有返回值,則把a(bǔ)wait后面的代碼放到Task中的ContinueWith函數(shù)體內(nèi),如果有返回值,則把Await后的結(jié)果轉(zhuǎn)化為訪問Task.Result屬性,文字說的可能不明白,看下示例代碼
//無返回值轉(zhuǎn)換前
public async void Example()
{
Task t = Task.Run(() =>
{
Thread.Sleep(1000);
});
await t;
//做一些工作
}
//無返回值轉(zhuǎn)換后
public void Example()
{
Task t = Task.Run(() =>
{
Thread.Sleep(1000);
});
t.ContinueWith(task =>
{
//做一些工作
});
}
//有返回值轉(zhuǎn)換前
public async void Example()
{
Task<int> t = Task.Run<int>(() =>
{
Thread.Sleep(1000);
return 1;
});
int res = await t;
//使用res做一些工作
}
//有返回值轉(zhuǎn)換后
public void Example()
{
Task<int> t = Task.Run<int>(() =>
{
Thread.Sleep(1000);
return 1;
});
t.ContinueWith(task =>
{
//使用task.Result做一些工作
});
}
看起來不錯(cuò),但至少有以下問題,如下:
該種轉(zhuǎn)換方法不能很好的轉(zhuǎn)換Try/Catch結(jié)構(gòu) 在循環(huán)結(jié)構(gòu)中使用await不好轉(zhuǎn)換 該實(shí)現(xiàn)與Task類型緊密聯(lián)系
一二點(diǎn)是我自己認(rèn)為的,但第三點(diǎn)是可以從擴(kuò)展async/await這點(diǎn)被證明的。但無論怎樣,async/await只是對(duì)方法按照一定的規(guī)則進(jìn)行了變換而已,它并沒有什么特別之處,具體來講,就是把Await后面要執(zhí)行的代碼放到一個(gè)類似ContinueWith的函數(shù)中,在C#中,它是以狀態(tài)機(jī)的形式表現(xiàn)的,每個(gè)狀態(tài)都對(duì)應(yīng)一部分代碼,狀態(tài)機(jī)有一個(gè)MoveNext()方法,MoveNext()根據(jù)不同的狀態(tài)執(zhí)行不同的代碼,然后每個(gè)狀態(tài)部分對(duì)應(yīng)的代碼都會(huì)設(shè)置下一個(gè)狀態(tài)字段,然后把自身的MoveNext()方法放到類似ContinueWith()的函數(shù)中去執(zhí)行,整個(gè)狀態(tài)機(jī)由回調(diào)函數(shù)推動(dòng)。我們嘗試手動(dòng)轉(zhuǎn)換以下async/await方法。
public static Task WorkAsync()
{
return Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Done!");
});
}
public static async void Test()
{
Console.WriteLine("步驟1");
await WorkAsync();
Console.WriteLine("步驟2");
await WorkAsync();
Console.WriteLine("步驟3");
}
手動(dòng)寫一個(gè)簡(jiǎn)單的狀態(tài)機(jī)類
public class TestAsyncStateMachine
{
public int _state = 0;
public void Start() => MoveNext();
public void MoveNext()
{
switch(_state)
{
case 0:
{
goto Step0;
}
case 1:
{
goto Step1;
}
default:
{
Console.WriteLine("步驟3");
return;
}
}
Step0:
{
Console.WriteLine("步驟1");
_state = 1;
WorkAsync().ContinueWith(t => this.MoveNext());
return;
}
Step1:
{
_state = -1;
Console.WriteLine("步驟2");
WorkAsync().ContinueWith(t => this.MoveNext());
return;
}
}
}
而Test()方法則變成了這樣
public static void Test()
{
new TestAsyncStateMachine().Start();
}
注意Test()方法返回的是void,這意味這調(diào)用方將不能await Test()。如果返回Task,這個(gè)狀態(tài)機(jī)類是不能正確處理的,如果要正確處理,那么狀態(tài)機(jī)在Start()啟動(dòng)后,必須返回一個(gè)Task,而這個(gè)Task在整個(gè)狀態(tài)機(jī)流轉(zhuǎn)完畢后要變成完成狀態(tài),以便調(diào)用方在該Task上調(diào)用的ContinueWith得以繼續(xù)執(zhí)行,而就Task這個(gè)類而言,它是沒有提供這種方法來主動(dòng)控制Task的狀態(tài)的,這個(gè)與JS中的Promise不同,JS里面用Reslove函數(shù)來主動(dòng)控制Promise的狀態(tài),并導(dǎo)致在該P(yáng)romise上面的Then鏈?zhǔn)秸{(diào)用得以繼續(xù)完成,而在C#里面怎么做呢?既然使用了狀態(tài)機(jī)來實(shí)現(xiàn)async/await,那么在轉(zhuǎn)換一個(gè)返回Task的函數(shù)時(shí)肯定會(huì)遇到,怎么處理?后面講。
首先解決一下與Task類型緊密聯(lián)系這個(gè)問題。
從狀態(tài)機(jī)中可以看到,主要使用到了Task中的ContinueWith這個(gè)函數(shù),它的語義是在任務(wù)完成后,執(zhí)行回調(diào)函數(shù),通過回調(diào)函數(shù)拿到結(jié)果,這個(gè)編程風(fēng)格也叫做CPS(Continuation-Passing-Style, 續(xù)體傳遞風(fēng)格),那么我們能不能把這個(gè)函數(shù)給抽象出來呢?語言開發(fā)者當(dāng)然想到了,它被抽象成了一個(gè)Awaiter因此編譯器要求await的類型必須要有GetAwaiter方法,什么樣的類型才是Awaiter呢?編譯器規(guī)定主要實(shí)現(xiàn)了如下幾個(gè)方法的類型就是Awaiter:
必須繼承INotifyCompletion接口,并實(shí)現(xiàn)其中的OnCompleted(Action continuation)方法 必須包含IsCompleted屬性 必須包含GetResult()方法
第一點(diǎn)好理解,第二點(diǎn)的作用是熱路徑優(yōu)化,第三點(diǎn)以后講。我們?cè)俑脑煲幌挛覀兪謩?dòng)寫的狀態(tài)機(jī)。
public class TestAsyncStateMachine
{
public int _state = 0;
public void Start() => MoveNext();
public void MoveNext()
{
switch(_state)
{
case 0:
{
goto Step0;
}
case 1:
{
goto Step1;
}
default:
{
Console.WriteLine("步驟3");
return;
}
}
Step0:
{
Console.WriteLine("步驟1");
_state = 1;
TaskAwaiter taskAwaiter;
taskAwaiter = WorkAsync().GetAwaiter();
if (taskAwaiter.IsCompleted) goto Step1;
taskAwaiter.OnCompleted(() => this.MoveNext());
return;
}
Step1:
{
_state = -1;
Console.WriteLine("步驟2");
TaskAwaiter taskAwaiter;
taskAwaiter = WorkAsync().GetAwaiter();
if (taskAwaiter.IsCompleted) MoveNext();
taskAwaiter.OnCompleted(() => this.MoveNext());
return;
}
}
}
可以看到去掉了與Task中ContinueWith的耦合關(guān)系,并且如果任務(wù)已經(jīng)完成,則可以直接執(zhí)行下個(gè)任務(wù),避免了無用的開銷。??因此我們可以總結(jié)一下async/await:
async/await只是表示這個(gè)方法需要編譯器進(jìn)行特殊處理,并不代表它本身一定是異步的。 Task類中的GetAwaiter主要是給編譯器用的。
//該類型包含GetAwaiter方法,且GetAwaiter()返回的類型包含三個(gè)必要條件
public class MyAwaiter : INotifyCompletion
{
public void OnCompleted(Action continuation)
{
continuation();
}
public bool IsCompleted { get; }
public void GetResult()
{
}
public MyAwaiter GetAwaiter() => new MyAwaiter();
}
一個(gè)測(cè)試函數(shù),注意必須返回void
public static async void AwaiterTest()
{
await new MyAwaiter();
Console.WriteLine("Done");
}
可以看到這是完全同步進(jìn)行的。
轉(zhuǎn)自:白煙染黑墨
鏈接:cnblogs.com/hkfyf/p/14641844.html


為什么阿里巴巴禁止使用存儲(chǔ)過程?

去TM收費(fèi),我要在線 Vip 視頻解析!
