はじめに
現在参画している案件にて、ValueTask<T>という型を使用しているのを見て「何なのだこれは!?」と思い勉強したので理解した範囲でまとめようと思います。
参照型のTaskに対してValueTask<T>は値型
もともとC#で非同期処理といえばTaskを使用するのが一般的なイメージでしたが、例えば以下のような処理を行うとします。
public async Task<int> TestAsync(bool isHeavy)
{
//基本的にほぼほぼisHeavyはfalseとなる
if (isHeavy)
{
//非同期処理
await xxxxAsync();
return 0;
}
else
{
return 0;
}
}
この処理ではほぼほぼisHeavyがtrueになりません。
そのため無駄にTaskクラスが生成されてしまっています。これくらいの規模ならばあまり気にならないかもしれませんが、膨大なソースコードになればなるほどこういうコツコツとしたところでパフォーマンスが悪くなっていきます(オーバーヘッド)。
ここへ値型であるValueTask構造体を利用することで必要のないインスタンスを生成する必要がなくなります。
ValueTask<T>の注意点
ValueTask構造体は内部でTaskと値型Tを抱えています。
よって毎回非同期処理を行う場合(上記のコードでいうとifの中に高確率で入ってくる場合)はただただTask + Tの構造体が作られる(前述のTaskクラスが作られる分よりもT分大きい)ので余計にパフォーマンスが悪くなってしまいます。
C#7.0のIValueTaskSourceで改善されたらしいのでそこはまたいずれ調べてみよう~