如何使用 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#中普通快取的使用C#快取
- 如何在C#中使用MSMQC#MQ
- 如何在 C# 中使用 ChannelsC#
- C#中的char和string的使用簡介C#
- 【swagger】C# 中 swagger 的使用及避坑SwaggerC#
- C#/Vsto中CustomTaskPanes和Ribbon的使用方法C#
- [C#]C#中字串的操作C#字串
- 使用C#如何監控選定資料夾中檔案的變動情況?C#
- 如何在C#專案中使用NHibernateC#
- 來看看如何在 C# 中使用反射C#反射
- C# ExpandoObject的使用C#Object
- C# RabbitMQ的使用C#MQ
- async/await 在 C# 語言中是如何工作的?(中)AIC#
- C# 中的“智慧列舉”:如何在列舉中增加行為C#
- C#中的MVCC#MVC
- C#中的MVVMC#MVVM
- c#中的事件C#事件
- C#中的集合C#
- C# 9.0中引入的新特性init和record的使用思考C#
- 如何在C#中使用Google.Protobuf工具C#Go
- 如何使用python中的opengl?Python
- C#分散式專案中是否使用MassTransitC#分散式
- 如何在C#中除錯LINQ查詢C#除錯
- c#中lock的使用(用於預約超出限額的流程)C#
- C#中Enum的用法C#
- 理解C#中的ConfigureAwaitC#AI
- C# 中的特性 AttributeC#
- c#中的委託C#
- c#中HttpWebRequest使用Proxy實現指定IP的域名請求C#HTTPWeb
- C# 左移右移在資料型別轉換中的使用C#資料型別
- C#二維陣列在SLG中的實現和使用C#陣列
- C# 如何重複呼叫父窗體中的子窗體C#
- C# 遞迴的使用案例C#遞迴
- C#如何建立一個可快速重複使用的專案模板C#
- C# - 如何在 MVVM 中處理 XAML 鍵盤?C#MVVM