如何使用 C# 中的 ValueTask
在 C# 中利用
ValueTask
避免從非同步方法返回
Task
物件時分配
ValueTask
避免從非同步方法返回
Task
物件時分配翻譯自 Joydip Kanjilal 2020年7月6日 的文章
非同步程式設計已經使用了相當長一段時間了。近年來,隨著
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 中建立一個 .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..."); else Console.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
之前,您都應該執行必要的效能分析。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984138/viewspace-2732620/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 理解C#中的ValueTaskC#
- 如何使用 C# 中的 LazyC#
- C#:終於有人把 ValueTask、IValueTaskSource、ManualResetValueTaskSourceCore 說清楚了!C#
- C#中WebBrowser的使用C#Web
- C#中using的使用C#
- C#中普通快取的使用C#快取
- C#中類的使用舉例C#
- C#中的ThenBy是如何實現的C#
- C#中列舉型別的使用C#型別
- 如何在 C# 中使用 ChannelsC#
- 如何在C#中使用MSMQC#MQ
- C# httpcookie asp.net中cookie的使用C#HTTPCookieASP.NET
- C# winfrom 中datagridview中checkbox的使用方法C#View
- C#中的char和string的使用簡介C#
- C#中多執行緒 委託的使用C#執行緒
- 使用C#如何監控選定資料夾中檔案的變動情況?C#
- C# 中的“智慧列舉”:如何在列舉中增加行為C#
- c#中datagridview裡checkbox的使用方法C#View
- 來看看如何在 C# 中使用反射C#反射
- C#如何使用 Debug 和 Trace 類C#
- C#中的集合C#
- C#中的delegateC#
- C#中的MVCC#MVC
- C#中的MVVMC#MVVM
- c#中如何使用列表datagridview新增修改刪除直接同步到oracleC#ViewOracle
- async/await 在 C# 語言中是如何工作的?(中)AIC#
- 如何使用python中的opengl?Python
- Java 中的陣列 如何使用Java陣列
- C# 9.0中引入的新特性init和record的使用思考C#
- C#中的ref和out的意義和使用方法C#
- 如何在C#中模擬C++的聯合(Union)?[C#, C++] How To Simulate C++ Union In C#?C#C++
- C# RabbitMQ的使用C#MQ
- C# ExpandoObject的使用C#Object
- 使用C# 探索 ML.NET 中的不同機器學習任務C#機器學習
- C#~非同步程式設計在專案中的使用C#非同步程式設計
- 如何在C#中使用Google.Protobuf工具C#Go
- 如何在C#專案中使用NHibernateC#
- C#中foreach基礎使用方法C#