Task VS ValueTask
生活随笔
收集整理的這篇文章主要介紹了
Task VS ValueTask
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
在 C# 中,異步編程是構(gòu)建響應(yīng)式應(yīng)用程序的基礎(chǔ)。Task 是表示異步操作的首選類型。但是,在某些高性能場景中,與 Task 相關(guān)的開銷可能會達到一個瓶頸。ValueTask 是 .NET Core 2.1 中引入的結(jié)構(gòu)。與引用類型的 Task 不同,ValueTask 是一種值類型,這使得它在某些情況下效率更高,尤其是在異步操作通常同步完成時。
1. Task 的特點
定義
- ?
Task是 C# 中表示異步操作的基礎(chǔ)類型。 - ? 它是一個引用類型,用于表示一個可能尚未完成的異步操作。
適用場景
- ? 適用于大多數(shù)異步操作,尤其是那些可能需要較長時間完成的操作(如 I/O 操作、網(wǎng)絡(luò)請求等)。
- ? 當異步操作的結(jié)果可能不會立即完成時,
Task是一個通用的選擇。
優(yōu)點
- ? 功能強大,支持復(fù)雜的異步操作。
- ? 可以表示沒有返回值(
Task)和有返回值(Task<T>)的異步操作。 - ? 支持任務(wù)組合(如
Task.WhenAll、Task.WhenAny)。
缺點
- ? 由于是引用類型,每次創(chuàng)建
Task都會在堆上分配內(nèi)存,可能對性能產(chǎn)生一定影響,尤其是在高頻調(diào)用的場景中。
2. ValueTask 的特點
定義
- ?
ValueTask是 C# 7.0 引入的一種輕量級的異步操作類型。 - ? 它是一個值類型,用于表示可能同步完成或異步完成的操作。
適用場景
- ? 適用于高頻調(diào)用的異步操作,尤其是那些可能經(jīng)常同步完成的操作。
- ? 當異步操作的結(jié)果可能立即完成時,
ValueTask可以避免不必要的堆分配,從而提高性能。
優(yōu)點
- ? 由于是值類型,
ValueTask在棧上分配內(nèi)存,避免了堆分配的開銷。 - ? 在同步完成的場景中,性能優(yōu)于
Task。 - ? 支持與
Task相同的功能,如await和異步操作組合。
缺點
- ? 功能相對簡單,不適合復(fù)雜的異步操作(均不支持任務(wù)組合、取消操作、任務(wù)狀態(tài)等等)。
- ? 由于是值類型,不能為
null,且不能直接轉(zhuǎn)換為Task。
3. ValueTask 和 Task 的區(qū)別
| 特性 | Task |
ValueTask |
|---|---|---|
| 類型 | 引用類型(class) | 值類型(struct) |
| 內(nèi)存分配 | 堆分配 | 棧分配(在同步完成時) |
| 性能 | 適用于大多數(shù)場景,但可能有堆分配開銷 | 在高頻調(diào)用或同步完成時性能更優(yōu) |
| 適用場景 | 通用異步操作 | 高頻調(diào)用或可能同步完成的異步操作 |
| 復(fù)雜性 | 功能強大,支持復(fù)雜操作 | 功能相對簡單 |
是否可為 null |
可以 | 不可以 |
4. 舉例說明
從緩存中讀取數(shù)據(jù)
假設(shè)有一個方法,嘗試從緩存中讀取數(shù)據(jù)。如果緩存中有數(shù)據(jù),則直接返回;如果沒有,則從數(shù)據(jù)庫異步獲取數(shù)據(jù)并緩存。
使用 Task 的實現(xiàn)
public async Task<ProductDto> GetProductAsync(int productId)
{
var key = $"Product_{productId}";
// 嘗試從緩存中同步獲取數(shù)據(jù)
if (_memoryCache.TryGetValue(key, out var cachedData))
{
return cachedData; // 如果數(shù)據(jù)在緩存中,直接返回
}
// 如果數(shù)據(jù)不在緩存中,異步獲取數(shù)據(jù)并緩存
var data = await _productRepo.GetDataAsync(productId);
_memoryCache.Set(key, data, TimeSpan.FromMinutes(60)); // 設(shè)置緩存過期時間
return data;
}
- ? 問題:
- ? 即使緩存命中(同步操作),
Task也會在堆上分配內(nèi)存。 - ? 如果緩存命中率很高,頻繁的內(nèi)存分配會影響性能。
- ? 即使緩存命中(同步操作),
使用 ValueTask 的實現(xiàn)
public async ValueTask<ProductDto> GetProductAsync(int productId)
{
var key = $"Product_{productId}";
// 嘗試從緩存中同步獲取數(shù)據(jù)
if (_memoryCache.TryGetValue(key, out var cachedData))
{
return cachedData; // 如果數(shù)據(jù)在緩存中,直接返回
}
// 如果數(shù)據(jù)不在緩存中,異步獲取數(shù)據(jù)并緩存
var data = await _productRepo.GetDataAsync(productId);
_memoryCache.Set(key, data, TimeSpan.FromMinutes(60)); // 設(shè)置緩存過期時間
return data;
}
- ? 優(yōu)點:
- ? 如果緩存命中(同步操作),
ValueTask不會在堆上分配內(nèi)存,性能更高。 - ? 如果緩存未命中(異步操作),
ValueTask會退化為Task,性能與Task相同。
- ? 如果緩存命中(同步操作),
ValueTask 的內(nèi)部結(jié)構(gòu)主要由以下兩部分組成:
- 1.
TResult:- ? 用于存儲同步操作的結(jié)果值。
- 2.
Task<TResult>或IValueTaskSource<TResult>:- ? 用于表示異步操作的任務(wù)。
通過這種設(shè)計,ValueTask 可以根據(jù)操作的實際完成方式(同步或異步)動態(tài)選擇最合適的實現(xiàn)方式。
5.如何選擇
| 場景 | 推薦類型 | 原因 |
|---|---|---|
| 大多數(shù)異步操作(如 I/O 操作) | Task |
代碼簡單,易于理解。 |
| 高頻調(diào)用(如緩存讀取) | ValueTask |
減少內(nèi)存分配,提升性能。 |
| 可能同步完成的操作 | ValueTask |
同步完成時不會分配堆內(nèi)存。 |
| 長時間運行的操作 | Task |
Task更適合長時間運行的異步操作。 |
需要多次 await的操作 |
Task |
ValueTask不能多次 await |
6. 注意事項
Task 的注意事項
- ? 內(nèi)存分配:
- ? 每次調(diào)用都會在堆上分配內(nèi)存,即使操作是同步完成的。
- ? 簡單性:
- ? 代碼更易于理解和維護。
ValueTask 的注意事項
- ? 不能多次
await:- ?
ValueTask只能被await一次,如果需要多次等待,應(yīng)先轉(zhuǎn)換為Task。 - ? 例如:
await (await GetProductAsync()).ConfigureAwait(false);是不允許的。
- ?
- ? 復(fù)雜性:
- ? 需要更多注意,避免誤用。
- ? 性能優(yōu)化:
- ? 只有在高頻調(diào)用或可能同步完成的場景下,
ValueTask的性能優(yōu)勢才明顯。
- ? 只有在高頻調(diào)用或可能同步完成的場景下,
7.總結(jié)
- ?
Task:- ? 適用于大多數(shù)異步場景,代碼簡單易用。
- ? 每次調(diào)用都會在堆上分配內(nèi)存。
- ?
ValueTask:- ? 適用于高頻調(diào)用或可能同步完成的場景,性能更高。
- ? 需要更多注意,避免誤用。
根據(jù)你的具體需求選擇合適的類型。如果性能是關(guān)鍵,且緩存命中率較高,推薦使用 ValueTask;否則,使用 Task 是更通用的選擇。
總結(jié)
以上是生活随笔為你收集整理的Task VS ValueTask的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信小程序,图片居中显示,适配不同机型
- 下一篇: Date对象