如何使用 C# 中的 ValueTask
在 C# 中利用?ValueTask?避免從異步方法返回?Task?對象時分配
翻譯自 Joydip Kanjilal 2020年7月6日 的文章?《How to use ValueTask in C#》(https://www.infoworld.com/article/3565433/how-to-use-valuetask-in-csharp.html)
異步編程已經使用了相當長一段時間了。近年來,隨著?async?和?await?關鍵字的引入,它變得更加強大。您可以利用異步編程來提高應用程序的響應能力和吞吐量。
C# 中異步方法的推薦返回類型是?Task。如果您想編寫一個有返回值的異步方法,那么應該返回?Task<T>; 如果想編寫事件處理程序,則可以返回?void。在 C# 7.0 之前,異步方法可以返回?Task、Task<T>?或?void。從 C# 7.0 開始,異步方法還可以返回?ValueTask(作為?System.Threading.Tasks.Extensions?包的一部分可用)或?ValueTask<T>。本文就討論一下如何在 C# 中使用?ValueTask。
要使用本文提供的代碼示例,您的系統中需要安裝 Visual Studio 2019。如果還沒有安裝,您可以在這里下載 Visual Studio 2019(https://visualstudio.microsoft.com/downloads/)。
在 Visual Studio 中創建一個 .NET Core 控制臺應用程序項目
首先,讓我們在 Visual Studio 中創建一個 .NET Core 控制臺應用程序項目。假設您的系統中安裝了 Visual Studio 2019,請按照下面描述的步驟在 Visual Studio 中創建一個新的 .NET Core 控制臺應用程序項目。
啟動 Visual Studio IDE。
點擊 “創建新項目”。
在 “創建新項目” 窗口中,從顯示的模板列表中選擇 “控制臺應用(.NET Core)”。
點擊 “下一步”。
在接下來顯示的 “配置新項目” 窗口,指定新項目的名稱和位置。
點擊 “創建”。
這將在 Visual Studio 2019 中創建一個新的 .NET Core 控制臺應用程序項目。我們將在本文后面的部分中使用這個項目來說明?ValueTask?的用法。
為什么要使用 ValueTask ?
Task?表示某個操作的狀態,即此操作是否完成、取消等。異步方法可以返回?Task?或者?ValueTask。
現在,由于?Task?是一個引用類型,從異步方法返回一個?Task?對象意味著每次調用該方法時都會在托管堆(managed heap)上分配該對象。因此,在使用?Task?時需要注意的一點是,每次從方法返回?Task?對象時都需要在托管堆中分配內存。如果你的方法執行的操作的結果立即可用或同步完成,則不需要這種分配,因此代價很高。
這正是?ValueTask?要出手相助的目的,ValueTask<T>?提供了兩個主要好處。首先,ValueTask<T>?提高了性能,因為它不需要在堆(heap)中分配; 其次,它的實現既簡單又靈活。當結果立即可用時,通過從異步方法返回?ValueTask<T>?代替?Task<T>,你可以避免不必要的分配開銷,因為這里的 “T” 表示一個結構,而 C# 中的結構體(struct)是一個值類型(與?Task<T>?中表示類的 “T” 不同)。
C# 中?Task?和?ValueTask?表示兩種主要的 “可等待(awaitable)” 類型。請注意,您不能阻塞(block)一個?ValueTask。如果需要阻塞,則應使用?AsTask?方法將?ValueTask?轉換為?Task,然后在該引用?Task?對象上進行阻塞。
另外請注意,每個?ValueTask?只能被消費(consumed)一次。這里的單詞 “消費(consume)”?是指?ValueTask?可以異步等待(await)操作完成,或者利用?AsTask?將?ValueTask?轉換為?Task。但是,ValueTask?只應被消費(consumed)一次,之后?ValueTask<T>?應被忽略。
C# 中的 ValueTask 示例
假設有一個異步方法返回一個?Task。你可以利用?Task.FromResult?創建?Task?對象,如下面給出的代碼片段所示。
public Task<int> GetCustomerIdAsync() {return Task.FromResult(1); }上面的代碼片段并沒有創建整個異步狀態機制,但它在托管堆(managed heap)中分配了一個?Task?對象。為了避免這種分配,您可能希望利用?ValueTask?代替,像下面給出的代碼片段所示的那樣。
public ValueTask<int> GetCustomerIdAsync() {return new ValueTask<int>(1); }下面的代碼片段演示了?ValueTask?的同步實現。
public interface IRepository<T> {ValueTask<T> GetData(); }Repository?類擴展了?IRepository?接口,并實現了如下所示的方法。
public class Repository<T> : IRepository<T> {public ValueTask<T> GetData(){var value = default(T);return new ValueTask<T>(value);} }下面是如何從?Main?方法調用?GetData?方法。
static void Main(string[] args) {IRepository<int> repository = new Repository<int>();var result = repository.GetData();if (result.IsCompleted)Console.WriteLine("Operation complete...");elseConsole.WriteLine("Operation incomplete...");Console.ReadKey(); }現在讓我們將另一個方法添加到我們的存儲庫(repository)中,這次是一個名為?GetDataAsync?的異步方法。以下是修改后的?IRepository?接口的樣子。
public interface IRepository<T> {ValueTask<T> GetData();ValueTask<T> GetDataAsync(); }GetDataAsync?方法由?Repository?類實現,如下面給出的代碼片段所示。
public class Repository<T> : IRepository<T> {public ValueTask<T> GetData(){var value = default(T);return new ValueTask<T>(value);}public async ValueTask<T> GetDataAsync(){var value = default(T);await Task.Delay(100);return value;} }C# 中應該在什么時候使用 ValueTask ?
盡管?ValueTask?提供了一些好處,但是使用?ValueTask?代替?Task?有一定的權衡。ValueTask?是具有兩個字段的值類型,而?Task?是具有單個字段的引用類型。因此,使用?ValueTask?意味著要處理更多的數據,因為方法調用將返回兩個數據字段而不是一個。另外,如果您等待(await)一個返回?ValueTask?的方法,那么該異步方法的狀態機也會更大,因為它必須容納一個包含兩個字段的結構體而不是在使用?Task?時的單個引用。
此外,如果異步方法的使用者使用?Task.WhenAll?或者?Task.WhenAny,在異步方法中使用?ValueTask<T>?作為返回類型可能會代價很高。這是因為您需要使用?AsTask?方法將?ValueTask<T>?轉換為?Task<T>,這會引發一個分配,而如果使用起初緩存的?Task<T>,則可以輕松避免這種分配。
經驗法則是這樣的:當您有一段代碼總是異步的時,即當操作(總是)不能立即完成時,請使用?Task。當異步操作的結果已經可用時,或者當您已經緩存了結果時,請利用?ValueTask。不管怎樣,在考慮使用?ValueTask?之前,您都應該執行必要的性能分析。
ValueTask?是?readonly struct?類型,Task?是?class?類型。
相關鏈接:C# 中 Struct 和 Class 的區別總結。
作者 :Joydip Kanjilal?
譯者 :技術譯民
出品 :技術譯站(https://ITTranslator.cn/)
總結
以上是生活随笔為你收集整理的如何使用 C# 中的 ValueTask的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 跟我一起学.NetCore之日志(Log
- 下一篇: asp.net core 从 3.1 到