最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Webpack5入門與實戰(zhàn),前端開發(fā)必備技能-碧山留豈得,芳草怨相違

2023-04-06 01:49 作者:bili_68802470155  | 我要投稿


async/await 在 C# 言語中是如何工作的?

Webpack5入門與實戰(zhàn),前端開發(fā)必備技能

download:https://www.zxit666.com/5600/

它提供了對平臺的高層次概述,總結(jié)了各種組件和設(shè)計決策,并承諾對所觸及的范疇發(fā)表更深化的文章。這是第一篇這樣深化討論 C# 和 .NET 中 async/await 的歷史、背后的設(shè)計決策和完成細節(jié)的文章。

對 async/await 的支持曾經(jīng)存在了十年之久。在這段時間里,它改動了為 .NET 編寫可擴展代碼的方式,而在不理解其底層邏輯的狀況下運用該功用是可行的,也是十分常見的。在這篇文章中,我們將深化討論 await 在言語、編譯器和庫級別的工作原理,以便你能夠充沛應(yīng)用這些有價值的功用。

不過,要做到這一點,我們需求追溯到 async/await 之前,以理解在沒有它的狀況下最先進的異步代碼是什么樣子的。

最初的樣子
早在 .NET Framework 1.0中,就有異步編程模型形式,又稱 APM 形式、Begin/End 形式、IAsyncResult 形式。在高層次上,該形式很簡單。關(guān)于同步操作 DoStuff:

class Handler
{
public int DoStuff(string arg);
}
作為形式的一局部,將有兩個相應(yīng)的辦法:BeginDoStuff 辦法和 EndDoStuff 辦法:

class Handler
{
public int DoStuff(string arg);
public IAsyncResult BeginDoStuff(string arg, AsyncCallback? callback, object? state);
public int EndDoStuff(IAsyncResult asyncResult);
}
BeginDoStuff 會像 DoStuff 一樣承受一切相同的參數(shù),但除此之外,它還會承受 AsyncCallback 拜托和一個不透明的狀態(tài)對象,其中一個或兩個都能夠為 null。Begin 辦法擔任初始化異步操作,假如提供了回調(diào)(通常稱為初始操作的“持續(xù)”),它還擔任確保在異步操作完成時調(diào)用回調(diào)。Begin 辦法還將結(jié)構(gòu)一個完成了 IAsyncResult 的類型實例,運用可選狀態(tài)填充 IAsyncResult 的 AsyncState 屬性:



namespace System
{
public interface IAsyncResult
{
object? AsyncState { get; }
WaitHandle AsyncWaitHandle { get; }
bool IsCompleted { get; }
bool CompletedSynchronously { get; }
}
public delegate void AsyncCallback(IAsyncResult ar);
}
然后,這個 IAsyncResult 實例將從 Begin 辦法返回,并在最終調(diào)用 AsyncCallback 時傳送給它。當準備運用操作的結(jié)果時,調(diào)用者將把 IAsyncResult 實例傳送給 End 辦法,該辦法擔任確保操作已完成(假如沒有完成,則經(jīng)過阻塞同步等候操作完成),然后返回操作的任何結(jié)果,包括傳播可能發(fā)作的任何錯誤和異常。因而,不用像下面這樣寫代碼來同步執(zhí)行操作:

try
{
int i = handler.DoStuff(arg);
Use(i);
}
catch (Exception e)
{
... // handle exceptions from DoStuff and Use
}
能夠按以下方式運用 Begin/End 辦法異步執(zhí)行相同的操作:

try
{
handler.BeginDoStuff(arg, iar =>
{
try
{
Handler handler = (Handler)iar.AsyncState!;
int i = handler.EndDoStuff(iar);
Use(i);
}
catch (Exception e2)
{
... // handle exceptions from EndDoStuff and Use
}
}, handler);
}
catch (Exception e)
{
... // handle exceptions thrown from the synchronous call to BeginDoStuff
}
關(guān)于在任何言語中處置過基于回調(diào)的 API 的人來說,這應(yīng)該覺得很熟習(xí)。

但是,事情從此變得愈加復(fù)雜。例如,有一個"stack dives"的問題。stack dives 是指代碼重復(fù)調(diào)用,在堆棧中越陷越深,以致于可能呈現(xiàn)堆棧溢出。假如操作同步完成,Begin 辦法被允許同步伐用回調(diào),這意味著對 Begin 的調(diào)用自身可能直接調(diào)用回調(diào)。同步完成的 "異步 "操作實踐上是很常見的;它們不是 "異步",由于它們被保證異步完成,而只是被允許這樣做。


這是一種真實的可能性,很容易再現(xiàn)。在 .NET Core 上試試這個程序:

using System.NET;
using System.NET.Sockets;
using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
listener.Listen();
using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.Connect(listener.LocalEndPoint!);
using Socket server = listener.Accept();
_ = server.SendAsync(new byte[100_000]);
var mres = new ManualResetEventSlim();
byte[] buffer = new byte[1];
var stream = new NetworkStream(client);
void ReadAgain()
{
stream.BeginRead(buffer, 0, 1, iar =>
{
if (stream.EndRead(iar) != 0)
{
ReadAgain(); // uh oh!
}
else
{
mres.Set();
}
}, null);
};
ReadAgain();
mres.Wait();
在這里,我設(shè)置了一個互相銜接的簡單客戶端套接字和效勞器套接字。效勞器向客戶端發(fā)送100,000字節(jié),然后客戶端繼續(xù)運用 BeginRead/EndRead 來“異步”地每次讀取一個字節(jié)。傳給 BeginRead 的回調(diào)函數(shù)經(jīng)過調(diào)用 EndRead 來完成讀取,然后假如它勝利讀取了所需的字節(jié),它會經(jīng)過遞歸調(diào)用 ReadAgain 部分函數(shù)來發(fā)出另一個 BeginRead。但是,在 .NET Core 中,套接字操作比在 .NET Framework 上快得多,并且假如操作系統(tǒng)可以滿足同步操作,它將同步完成(留意內(nèi)核自身有一個緩沖區(qū)用于滿足套接字接納操作)。因而,這個堆棧會溢出:

因而,APM 模型中內(nèi)置了補償機制。有兩種可能的辦法能夠補償這一點:

1.不要允許 AsyncCallback 被同步伐用。假如不斷異步伐用它,即便操作以同步方式完成,那么 stack dives 的風(fēng)險也會消逝。但是性能也是如此,由于同步完成的操作(或者快到無法察看到它們的區(qū)別)是十分常見的,強迫每個操作排隊回調(diào)會增加可丈量的開支。
2.運用一種機制,允許調(diào)用方而不是回調(diào)方在操作同步完成時執(zhí)行持續(xù)工作。這樣,您就能夠避開額外的辦法框架,繼續(xù)執(zhí)行后續(xù)工作,而不深化堆棧。

APM 形式與辦法2一同運用。為此,IAsyncResult 接口公開了兩個相關(guān)但不同的成員:IsCompleted 和 CompletedSynchronously。IsCompleted 通知你操作能否曾經(jīng)完成,能夠?qū)掖螜z查它,最終它會從 false 轉(zhuǎn)換為 true,然后堅持不變。相比之下,CompletedSynchronously 永遠不會改動(假如改動了,那就是一個令人厭惡的 bug)。它用于 Begin 辦法的調(diào)用者和 AsyncCallback 之間的通訊,他們中的一個擔任執(zhí)行任何持續(xù)工作。假如 CompletedSynchronously 為 false,則操作是異步完成的,響應(yīng)操作完成的任何后續(xù)工作都應(yīng)該留給回調(diào);畢竟,假如工作沒有同步完成,Begin 的調(diào)用方無法真正處置它,由于還不曉得操作曾經(jīng)完成(假如調(diào)用方只是調(diào)用 End,它將阻塞直到操作完成)。但是,假如 CompletedSynchronously 為真,假如回調(diào)要處置持續(xù)工作,那么它就有 stack dives 的風(fēng)險,由于它將在堆棧上執(zhí)行比開端時更深的持續(xù)工作。因而,任何觸及到這種堆棧潛水的完成都需求檢查 CompletedSynchronously,并讓 Begin 辦法的調(diào)用者執(zhí)行持續(xù)工作(假如它為真),這意味著回調(diào)不需求執(zhí)行持續(xù)工作。這也是 CompletedSynchronously 永遠不能更改的緣由,調(diào)用方和回調(diào)方需求看到相同的值,以確保不論競爭條件如何,持續(xù)工作只執(zhí)行一次。

我們都習(xí)氣了現(xiàn)代言語中的控制流構(gòu)造為我們提供的強大和簡單性,一旦引入了任何合理的復(fù)雜性,而基于回調(diào)的辦法通常會與這種構(gòu)造相抵觸。其他主流言語也沒有更好的替代計劃。

我們需求一種更好的辦法,一種從 APM 形式中學(xué)習(xí)的辦法,交融它正確的東西,同時防止它的圈套。值得留意的是,APM 形式只是一種形式。運轉(zhuǎn)時間、中心庫和編譯器在運用或完成該形式時并沒有提供任何協(xié)助。

基于事情的異步形式
.NET Framework 2.0引入了一些 API,完成了處置異步操作的不同形式,這種形式主要用于在客戶端應(yīng)用程序上下文中處置異步操作。這種基于事情的異步形式或 EAP 也作為一對成員呈現(xiàn),這次是一個用于初始化異步操作的辦法和一個用于偵聽其完成的事情。因而,我們之前的 DoStuff 示例可能被公開為一組成員,如下所示:

class Handler
{
public int DoStuff(string arg);
public void DoStuffAsync(string arg, object? userToken);
public event DoStuffEventHandler? DoStuffCompleted;
}
public delegate void DoStuffEventHandler(object sender, DoStuffEventArgs e);
public class DoStuffEventArgs : AsyncCompletedEventArgs
{
public DoStuffEventArgs(int result, Exception? error, bool canceled, object? userToken) :
base(error, canceled, usertoken) => Result = result;
public int Result { get; }
}
你需求用 DoStuffCompleted 事情注冊你的后續(xù)工作,然后調(diào)用 DoStuffAsync 辦法;它將啟動該操作,并且在該操作完成時,調(diào)用者將異步地引發(fā) DoStuffCompleted 事情。然后,處置程序能夠繼續(xù)執(zhí)行后續(xù)工作,可能會考證所提供的 userToken 與它所希冀的停止匹配,從而允許多個處置程序同時銜接到事情。

這種形式使一些用例變得更簡單,同時使其他用例變得愈加艱難(思索到前面的 APM CopyStreamToStream 示例,這闡明了一些問題)。它沒有以普遍的方式推出,只是在一個單獨的 .NET Framework 版本中匆匆的呈現(xiàn)又消逝了,雖然留下了它運用期間添加的 api,如 Ping.SendAsync/Ping.PingCompleted:

public class Ping : Component
{
public void SendAsync(string hostNameOrAddress, object? userToken);
public event PingCompletedEventHandler? PingCompleted;
...
}
但是,它的確獲得了一個 APM 形式完整沒有思索到的顯著進步,并且這一點不斷持續(xù)到我們今天所承受的模型中: SynchronizationContext。

思索到像 Windows Forms 這樣的 UI 框架。與 Windows 上的大多數(shù) UI 框架一樣,控件與特定的線程相關(guān)聯(lián),該線程運轉(zhuǎn)一個音訊泵,該音訊泵運轉(zhuǎn)可以與這些控件交互的工作,只要該線程應(yīng)該嘗試操作這些控件,而任何其他想要與控件交互的線程都應(yīng)該經(jīng)過發(fā)送音訊由 UI 線程的泵耗費來完成操作。Windows 窗體運用 ControlBeginInvoke 等辦法使這變得很容易,它將提供的拜托和參數(shù)排隊,由與該控件相關(guān)聯(lián)的任何線程運轉(zhuǎn)。因而,你能夠這樣編寫代碼:

private void button1_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(_ =>
{
string message = ComputeMessage();
button1.BeginInvoke(() =>
{
button1.Text = message;
});
});
}
這將卸載在 ThreadPool 線程上完成的 ComputeMessage()工作(以便在處置 UI 的過程中堅持 UI 的響應(yīng)性),然后在工作完成時,將拜托隊列返回到與 button1 相關(guān)的線程,以更新 button1 的標簽。這很簡單,WPF 也有相似的東西,只是用它的 Dispatcher 類型:

private void button1_Click(object sender, RoutedEventArgs e){
ThreadPool.QueueUserWorkItem(_ =>
{
string message = ComputeMessage();
button1.Dispatcher.InvokeAsync(() =>
{
button1.Content = message;
});
});}
.NET MAUI 也有相似的功用。但假如我想把這個邏輯放到輔助辦法中呢?

E.g.

// Call ComputeMessage and then invoke the update action to update controls.internal static void ComputeMessageAndInvokeUpdate(Action update) { ... }
然后我能夠這樣運用它:

private void button1_Click(object sender, EventArgs e){
ComputeMessageAndInvokeUpdate(message => button1.Text = message);}
但是如何完成 ComputeMessageAndInvokeUpdate,使其可以在這些應(yīng)用程序中工作呢?能否需求硬編碼才干理解每個可能的 UI 框架?這就是 SynchronizationContext 的魅力所在。我們能夠這樣完成這個辦法:

internal static void ComputeMessageAndInvokeUpdate(Action<string> update){
SynchronizationContext? sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_ =>
{
string message = ComputeMessage();
if (sc is not null)
{
sc.Post(_ => update(message), null);
}
else
{
update(message);
}
});}
它運用 SynchronizationContext 作為一個籠統(tǒng),目的是任何“調(diào)度器”,應(yīng)該用于回到與 UI 交互的必要環(huán)境。然后,每個應(yīng)用程序模型確保它作為 SynchronizationContext.Current 發(fā)布一個 SynchronizationContext-derived 類型,去做 "正確的事情"。例如,Windows Forms 有這個:

public sealed class WindowsFormsSynchronizationContext : SynchronizationContext, IDisposable{
public override void Post(SendOrPostCallback d, object? state) =>
_controlToSendTo?.BeginInvoke(d, new object?[] { state });
...}
WPF 有這個:

public sealed class DispatcherSynchronizationContext : SynchronizationContext{
public override void Post(SendOrPostCallback d, Object state) =>
_dispatcher.BeginInvoke(_priority, d, state);
...}
ASP.NET 曾經(jīng)有一個,它實踐上并不關(guān)懷工作在什么線程上運轉(zhuǎn),而是關(guān)懷給定的懇求相關(guān)的工作被序列化,這樣多個線程就不會并發(fā)地訪問給定的 HttpContext:

internal sealed class AspNetSynchronizationContext : AspNetSynchronizationContextBase{
public override void Post(SendOrPostCallback callback, Object state) =>
_state.Helper.QueueAsynchronous(() => callback(state));
...}
這也不限于這些主要的應(yīng)用程序模型。例如,xunit 是一個盛行的單元測試框架,是 .NET 中心存儲庫用于單元測試的框架,它也采用了多個自定義的 SynchronizationContext。例如,你能夠允許并行運轉(zhuǎn)測試,但限制允許并發(fā)運轉(zhuǎn)的測試數(shù)量。這是如何完成的呢?經(jīng)過 SynchronizationContext:

public class MaxConcurrencySyncContext : SynchronizationContext, IDisposable{
public override void Post(SendOrPostCallback d, object? state)
{
var context = ExecutionContext.Capture();
workQueue.Enqueue((d, state, context));
workReady.Set();
}}
MaxConcurrencySyncContext 的 Post 辦法只是將工作排到本人的內(nèi)部工作隊列中,然后在它本人的工作線程上處置它,它依據(jù)所需的最大并發(fā)數(shù)來控制有幾工作線程。

這與基于事情的異步形式有什么聯(lián)絡(luò)?EAP 和 SynchronizationContext 是同時引入的,當異步操作被啟動時,EAP 規(guī)則完成事情應(yīng)該排隊到當前任何 SynchronizationContext 中。為了略微簡化一下,System.ComponentModel 中也引入了一些輔助類型,特別是 AsyncOperation 和 AsyncOperationManager。前者只是一個元組,封裝了用戶提供的狀態(tài)對象和捕獲的 SynchronizationContext,然后者只是作為一個簡單的工廠來捕獲并創(chuàng)立 AsyncOperation 實例。然后 EAP 完成將運用這些,例如 Ping.SendAsync 調(diào)用 AsyncOperationManager.CreateOperation 來捕獲 SynchronizationContext。當操作完成時,AsyncOperation 的 PostOperationCompleted 辦法將被調(diào)用,以調(diào)用存儲的 SynchronizationContext 的 Post 辦法。

我們需求比 APM 形式更好的東西,接下來呈現(xiàn)的 EAP 引入了一些新的事務(wù),但并沒有真正處理我們面臨的中心問題。我們依然需求更好的東西。

輸入任務(wù)
.NET Framework 4.0 引入了 System.Threading.Tasks.Task 類型。從實質(zhì)上講,Task 只是一個數(shù)據(jù)構(gòu)造,表示某些異步操作的最終完成(其他框架將相似的類型稱為“promise”或“future”)。創(chuàng)立 Task 是為了表示某些操作,然后當它表示的操作邏輯上完成時,結(jié)果存儲到該 Task 中。但是 Task 提供的關(guān)鍵特性使它比 IAsyncResult 更有用,它在本人內(nèi)部內(nèi)置了 continuation 的概念。這一特性意味著您能夠訪問任何 Task,并在其完成時懇求異步通知,由任務(wù)自身處置同步,以確保繼續(xù)被調(diào)用,無論任務(wù)能否曾經(jīng)完成、尚未完成、還是與通知懇求同時完成。為什么會有如此大的影響?假如你還記得我們對舊 APM 形式的討論,有兩個主要問題。

你必需為每個操作完成一個自定義的 IAsyncResult 完成:沒有內(nèi)置的 IAsyncResult 完成,任何人都能夠依據(jù)需求運用。
在 Begin 辦法被調(diào)用之前,你必需曉得當它完成時要做什么。這使得完成組合器和其他用于耗費和組合恣意異步完成的通用例程成為一個嚴重應(yīng)戰(zhàn)。
如今,讓我們更好天文解它的實踐含義。我們先從幾個字段開端:

class MyTask{
private bool _completed;
private Exception? _error;
private Action? _continuation;
private ExecutionContext? _ec;
...}
我們需求一個字段來曉得任務(wù)能否完成(_completed),還需求一個字段來存儲招致任務(wù)失敗的任何錯誤(_error);假如我們還要完成一個通用的 MyTask,那么也會有一個私有的 TResult _result 字段,用于存儲操作的勝利結(jié)果。到目前為止,這看起來很像我們之前自定義的 IAsyncResult 完成(當然,這不是巧合)。但是如今最重要的局部,是 _continuation 字段。在這個簡單的完成中,我們只支持一個 continuation,但關(guān)于解釋目的來說這曾經(jīng)足夠了(真正的任務(wù)運用了一個對象字段,該字段能夠是單個 continuation 對象,也能夠是 continuation 對象的 List<>)。這是一個拜托,將在任務(wù)完成時調(diào)用。

如前所述,與以前的模型相比,Task 的一個根本進步是可以在操作開端后提供持續(xù)工作(回調(diào))。我們需求一個辦法來做到這一點,所以讓我們添加 ContinueWith:

public void ContinueWith(Action action){
lock (this)
{
if (_completed)
{
ThreadPool.QueueUserWorkItem(_ => action(this));
}
else if (_continuation is not null)
{
throw new InvalidOperationException("Unlike Task, this implementation only supports a single continuation.");
}
else
{
_continuation = action;
_ec = ExecutionContext.Capture();
}
}}
假如任務(wù)在 ContinueWith 被調(diào)用時曾經(jīng)被標志為完成,ContinueWith 只是排隊執(zhí)行拜托。否則,該辦法將存儲該拜托,以便在任務(wù)完成時能夠排隊繼續(xù)執(zhí)行(它還存儲了一個叫做 ExecutionContext 的東西,然后在以后調(diào)用該拜托時運用它)。

然后,我們需求可以將 MyTask 標志為完成,這意味著它所代表的異步操作曾經(jīng)完成。為此,我們將提供兩個辦法,一個用于標志完成(" SetResult "),另一個用于標志完成并返回錯誤(" SetException "):

public void SetResult() => Complete(null);
public void SetException(Exception error) => Complete(error);
private void Complete(Exception? error){
lock (this)
{
if (_completed)
{
throw new InvalidOperationException("Already completed");
}
_error = error;
_completed = true;
if (_continuation is not null)
{
ThreadPool.QueueUserWorkItem(_ =>
{
if (_ec is not null)
{
ExecutionContext.Run(_ec, _ => _continuation(this), null);
}
else
{
_continuation(this);
}
});
}
}}
我們存儲任何錯誤,將任務(wù)標志為已完成,然后假如之前曾經(jīng)注冊了 continuation,則將其排隊等候調(diào)用。

最后,我們需求一種辦法來傳播任務(wù)中可能發(fā)作的任何異常(并且,假如這是一個泛型 MyTask,則返回其_result);為了便當某些狀況,我們還允許此辦法阻塞等候任務(wù)完成,這能夠經(jīng)過 ContinueWith 完成(continuation 只是發(fā)出 ManualResetEventSlim 信號,然后調(diào)用者阻塞等候完成)。

public void Wait(){
ManualResetEventSlim? mres = null;
lock (this)
{
if (!_completed)
{
mres = new ManualResetEventSlim();
ContinueWith(_ => mres.Set());
}
}
mres?.Wait();
if (_error is not null)
{
ExceptionDispatchInfo.Throw(_error);
}}
根本上就是這樣。如今能夠肯定的是,真正的 Task 要復(fù)雜得多,有更高效的完成,支持恣意數(shù)量的 continuation,有大量關(guān)于它應(yīng)該如何表現(xiàn)的按鈕(例如,continuation 應(yīng)該像這里所做的那樣排隊,還是應(yīng)該作為任務(wù)完成的一局部同步伐用),可以存儲多個異常而不是一個異常,具有取消的特殊學(xué)問,有大量的輔助辦法用于執(zhí)行常見操作,例如 Task.Run,它創(chuàng)立一個 Task 來表示線程池上調(diào)用的拜托隊列等等。

你可能還留意到,我簡單的 MyTask 直接有公共的 SetResult/SetException 辦法,而 Task 沒有。實踐上,Task 的確有這樣的辦法,它們只是內(nèi)部的,System.Threading.Tasks.TaskCompletionSource 類型作為任務(wù)及其完成的獨立“消費者”;這樣做不是出于技術(shù)上的需求,而是為了讓完成辦法遠離只用于消費的東西。然后,你就能夠把 Task 分發(fā)進來,而不用擔憂它會在你下面完成;完成信號是創(chuàng)立任務(wù)的完成細節(jié),并且經(jīng)過保存 TaskCompletionSource 自身來保存完成它的權(quán)益。(CancellationToken 和 CancellationTokenSource 遵照相似的形式:CancellationToken 只是 CancellationTokenSource 的一個構(gòu)造封裝器,只提供與消費取消信號相關(guān)的公共區(qū)域,但沒有產(chǎn)生取消信號的才能,而產(chǎn)生取消信號的才能僅限于可以訪問 CancellationTokenSource的人。)

當然,我們能夠為這個 MyTask 完成組合器和輔助器,就像 Task 提供的那樣。想要一個簡單的 MyTask.WhenAll?

public static MyTask WhenAll(MyTask t1, MyTask t2){
var t = new MyTask();
int remaining = 2;
Exception? e = null;
Action continuation = completed =>
{
e ??= completed._error; // just store a single exception for simplicity
if (Interlocked.Decrement(ref remaining) == 0)
{
if (e is not null) t.SetException(e);
else t.SetResult();
}
};
t1.ContinueWith(continuation);
t2.ContinueWith(continuation);
return t;}
想要一個 MyTask.Run?你得到了它:

public static MyTask Run(Action action){
var t = new MyTask();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
action();
t.SetResult();
}
catch (Exception e)
{
t.SetException(e);
}
});
return t;}
一個 MyTask.Delay 怎樣樣?當然能夠:

public static MyTask Delay(TimeSpan delay){
var t = new MyTask();
var timer = new Timer(_ => t.SetResult());
timer.Change(delay, Timeout.InfiniteTimeSpan);
return t;}
有了 Task,.NET 中之前的一切異步形式都將成為過去。在以前運用 APM 形式或 EAP 形式完成異步完成的中央,都會公開新的 Task 返回辦法。

▌ValueTasks
時至今日,Task 依然是 .NET 中異步處置的主力,每次發(fā)布都有新辦法公開,并且在整個生態(tài)系統(tǒng)中都例行地返回 Task 和 Task。但是,Task 是一個類,這意味著創(chuàng)立一個類需求分配內(nèi)存。在大多數(shù)狀況下,為一個長期異步操作額外分配內(nèi)存是微乎其微的,除了對性能最敏感的操作之外,不會對一切操作的性能產(chǎn)生有意義的影響。不過,如前所述,異步操作的同步完成是相當常見的。引入 Stream.ReadAsync 是為了返回一個 Task,但假如你從一個 BufferedStream 中讀取數(shù)據(jù),很有可能很多讀取都是同步完成的,由于只需求從內(nèi)存中的緩沖區(qū)中讀取數(shù)據(jù),而不是執(zhí)行系統(tǒng)調(diào)用和真正的 I/O 操作。不得不分配一個額外的對象來返回這樣的數(shù)據(jù)是不幸的(留意,APM 也是這樣的狀況)。關(guān)于返回 Task 的非泛型辦法,該辦法能夠只返回一個曾經(jīng)完成的單例任務(wù),而實踐上 Task.CompletedTask 提供了一個這樣的單例 Task。但關(guān)于 Task 來說,不可能為每個可能的結(jié)果緩存一個 Task。我們能夠做些什么來讓這種同步完成更快呢?

緩存一些 Task 是可能的。例如,Task 十分常見,而且只要兩個有意義的東西需求緩存:當結(jié)果為 true 時,一個 Task,當結(jié)果為 false 時,一個 Task。或者,固然我們不想緩存40億個 Task 來包容一切可能的 Int32 結(jié)果,但小的 Int32 值是十分常見的,因而我們能夠緩存一些值,比方-1到8?;蛘哧P(guān)于恣意類型,default 是一個合理的通用值,因而我們能夠緩存每個相關(guān)類型的 Task,其中 Result 為 default(TResult)。事實上,Task.FromResult 今天也是這樣做的 (從最近的 .NET 版本開端),運用一個小型的可復(fù)用的 Task 單例緩存,并在恰當時返回其中一個,或者為精確提供的結(jié)果值分配一個新的 Task。能夠創(chuàng)立其他計劃來處置其他合理的常見狀況。例如,當運用 Stream.ReadAsync 時,在同一個流上屢次調(diào)用它是合理的,而且每次調(diào)用時允許讀取的字節(jié)數(shù)都是相同的。完成可以完整滿足 count 懇求是合理的。這意味著 Stream.ReadAsync 反復(fù)返回相同的 int 值是很常見的。為了防止這種狀況下的屢次分配,多個 Stream 類型(如 MemoryStream)會緩存它們最后勝利返回的 Task,假如下一次讀取也同步完成并勝利取得相同的結(jié)果,它能夠只是再次返回相同的 Task,而不是創(chuàng)立一個新的。但其他狀況呢?在性能開支十分重要的狀況下,如何更普遍地防止對同步完成的這種分配?

這就是 ValueTask 的作用。ValueTask 最初是作為 TResult 和 Task 之間的一個辨別并集。說到底,拋開那些花哨的東西,這就是它的全部 (或者,更確切地說,曾經(jīng)是),是一個即時的結(jié)果,或者是對將來某個時辰的一個結(jié)果的承諾:

public readonly struct ValueTask{
private readonly Task? _task;
private readonly TResult _result;
...}
然后,一個辦法能夠返回這樣一個 ValueTask,而不是一個 Task,假如 TResult 在需求返回的時分曾經(jīng)曉得了,那么就能夠防止 Task 的分配,代價是一個更大的返回類型和略微多一點間接性。

但是,在一些超級極端的高性能場景中,即便在異步完成的狀況下,您也希望可以防止 Task 分配。例如,Socket 位于網(wǎng)絡(luò)堆棧的底部,Socket 上的 SendAsync 和 ReceiveAsync 關(guān)于許多效勞來說是十分搶手的途徑,同步和異步完成都十分常見(大多數(shù)同步發(fā)送完成,許多同步接納完成,由于數(shù)據(jù)曾經(jīng)在內(nèi)核中緩沖了)。假如在一個給定的 Socket 上,我們能夠使這樣的發(fā)送和接納不受分配限制,而不論操作是同步完成還是異步完成,這不是很好嗎?

這就是 System.Threading.Tasks.Sources.IValueTaskSource 進入的中央:

public interface IValueTaskSource{
ValueTaskSourceStatus GetStatus(short token);
void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags);
TResult GetResult(short token);}
IValueTaskSource 接口允許一個完成為 ValueTask 提供本人的支持對象,使該對象可以完成像 GetResult 這樣的辦法來檢索操作的結(jié)果,以及 OnCompleted 來銜接操作的持續(xù)。就這樣,ValueTask 對其定義停止了一個小小的更改,其 Task? _task 字段交換為 object? _obj 字段:

public readonly struct ValueTask{
private readonly object? _obj;
private readonly TResult _result;
...}
以前 _task 字段要么是 Task 要么是 null,如今 _obj 字段也能夠是 IValueTaskSource。一旦 Task 被標志為已完成,它將堅持完成狀態(tài),并且永遠不會轉(zhuǎn)換回未完成的狀態(tài)。相比之下,完成 IValueTaskSource 的對象對完成有完整的控制權(quán),能夠自在地在完成狀態(tài)和不完成狀態(tài)之間雙向轉(zhuǎn)換,由于 ValueTask 的契約是一個給定的實例只能被耗費一次,因而從構(gòu)造上看,它不應(yīng)該察看到底層實例的耗費后變化(這就是 CA2012等剖析規(guī)則存在的緣由)。這就使得像 Socket 這樣的類型可以將 IValueTaskSource 的實例集中起來,用于反復(fù)調(diào)用。Socket 最多能夠緩存兩個這樣的實例,一個用于讀,一個用于寫,由于99.999%的狀況是在同一時間最多只要一個接納和一個發(fā)送。

我提到了 ValueTask,但沒有提到 ValueTask。當只處置防止同步完成的分配時,運用非泛型 ValueTask(代表無結(jié)果的無效操作)在性能上沒有什么益處,由于同樣的條件能夠用 Task.CompletedTask 來表示。但是,一旦我們關(guān)懷在異步完成的狀況下運用可池化的底層對象來防止分配的才能,那么這對非泛型也很重要。因而,當 IValueTaskSource 被引入時,IValueTaskSource 和 ValueTask 也被引入。

因而,我們有 Task、Task、ValueTask 和 ValueTask。我們可以以各種方式與它們交互,表示恣意的異步操作,并銜接 continuation 來處置這些異步操作的完成。

Webpack5入門與實戰(zhàn),前端開發(fā)必備技能-碧山留豈得,芳草怨相違的評論 (共 條)

分享到微博請遵守國家法律
莱芜市| 怀化市| 岫岩| 页游| 关岭| 勃利县| 葫芦岛市| 英德市| 马龙县| 江安县| 陇南市| 建始县| 策勒县| 奉节县| 绩溪县| 长白| 德江县| 都昌县| 济阳县| 阳泉市| 安庆市| 册亨县| 临澧县| 黔南| 宁强县| 灵璧县| 紫云| 柳河县| 奉新县| 天祝| 五常市| 黔西县| 阿荣旗| 山东| 东平县| 波密县| 本溪市| 广宗县| 大埔区| 莎车县| 军事|