C#でのValueTaskの使用方法
5044 ワード
は、特にC 5.0に導入されたawait、asyncキーワードがコードの作成をより簡単にし、
を利用してアプリケーションの
と
を高めることができると信じています.非同期メソッドではTaskを返すことが推奨されていますが、非同期メソッドでいくつかのデータを返す必要がある場合は、
Task
をTask
に変更することができます.また、あなたの非同期メソッドが何も戻る必要がない場合は、voidに変更することができます.C#7.0までの非同期メソッドの戻り値は、次の3つです.
Task
Task
void
C#7.0以降は、上記の3つの他に
ValueTask
とValueTask
を返すことができます.これらのクラスはSystem.Threading.Tasks.Extensions
のネーミングスペースの下で、この文章は皆さんと一緒にValueTaskについて議論するつもりです.なぜValueTaskを使うのか
Taskは操作の状態を表すことができます.どういう意味ですか.例えば、この操作は完了しましたか?キャンセルされるかどうかなど、非同期メソッドでTaskまたはValueTaskを返すことができます.ここで潜在的な問題は、Taskが参照タイプであるため、次のコードに注意しているかどうか分かりません.
public class Task : IAsyncResult, IDisposable
{
}
これは、非同期メソッドを呼び出すたびに
にTaskインスタンスが生成されることを意味し、瞬時に1 w回呼び出されると、管理スタックにも瞬時に1 wのTaskインスタンスが存在する場合、これはどのような問題があるのでしょうか.問題は、一部のシーンでは、あなたの非同期方法は直接データを返すことができ、あるいは完全に同期することができます.例えば、あなたの非同期方法はキャッシュからデータを取るだけで、GCに回収圧力を加えるのはそんなに美しくありません.
static Task GetCustomerCountAsyc(string key)
{
return Task.FromResult(NativeCache[key]);
}
この場合、ValueTaskを使用する必要があります.これは、次の2つのメリットを提供します.
総じて言えば、もしあなたの非同期方法が直接結果を得ることができるならば、
Task
をValueTask
に変更することを提案して、それによって不要な性能のオーバーヘッドを避けて、Task
とValueTask
はすべて待つことができる操作を表して、ここで注意しなければならないのは、決してValueTaskをブロックしないでください、結局それは値のタイプで、もし本当にそうするならば、ValueTask.AsTaskメソッドを呼び出してValueTaskをTaskに変換し、この参照のTask上でブロックします.もう一つ注意したいのは、ValueTaskはawait
回しかできません.この制限を破るには、AsTaskメソッドを呼び出してValueTaskをTaskに変換すればいいことです.ValueTaskの例
非同期メソッドがある場合、Taskを返す必要があります.
Task.FromResult
を使用してTaskオブジェクトを生成できます.次のコードに示します.
public Task GetCustomerIdAsync()
{
return Task.FromResult(1);
}
上のコードはILレベルで完全なステートマシンコードを生成するのではなく、管理スタックの中でTaskオブジェクトを生成するだけで、このような無意味なTask割り当てを避けるには、ValueTaskでそれを削除することができます.以下のコードに示します.
public ValueTask GetCustomerIdAsync()
{
return new ValueTask(1);
}
次にIrepositoryインタフェースを定義し、次のコードに示すように、
ValueTask
の戻り値を持つ同期方法を追加します.
public interface IRepository
{
ValueTask GetData();
}
次のコードに示すように、IrepositoryインタフェースからRepositoryクラスが派生します.
public class Repository : IRepository
{
public ValueTask GetData()
{
var value = default(T);
return new ValueTask(value);
}
}
最後にMainでGetDataメソッドを呼び出します.
static void Main(string[] args)
{
IRepository repository = new Repository();
var result = repository.GetData();
if(result.IsCompleted)
Console.WriteLine("Operation complete...");
else
Console.WriteLine("Operation incomplete...");
Console.ReadKey();
}
Irepositoryインタフェースに非同期メソッド
GetDataAsync
が追加され、変更されたコードは次のようになります.
public interface IRepository
{
ValueTask GetData();
ValueTask GetDataAsync();
}
public class Repository : IRepository
{
public ValueTask GetData()
{
var value = default(T);
return new ValueTask(value);
}
public async ValueTask GetDataAsync()
{
var value = default(T);
await Task.Delay(100);
return value;
}
}
いつValueTaskを使うべきか
ValueTaskは多くのメリットを提供していますが、ValueTaskをValueTaskに置き換えるには、ValueTaskは2つのフィールドを含む値タイプであり、Taskは1つのフィールドの参照タイプであるため、メソッドからValueTaskを返すと2つのフィールドのオーバーヘッドが発生することを意味します.同時にawaitシーンで生成される
は、2つのフィールドのValueTaskタイプを格納するためにより大きな空間を必要とする.さらに拡張すると、非同期メソッドの呼び出し者が
Task.WhenAll
またはTask.WhenAny
を使用する場合、この非同期メソッドの戻り値がValueTask
であるとオーバーヘッドが通常より大きくなるのはなぜですか?WhenXXXで使用するには、ValueTaskをTaskに変換する必要があります.このプロセスでは、管理スタックのメモリ割り当てが発生します.最適化するには、Taskを初めて使用するときに、このインスタンスをキャッシュして、後で再多重化することができます.最後に
をまとめましょう.いずれにしても、ValueTaskを使う前に必ず必要な性能分析をして、自分に十分な理由を与えてValueTaskを使います.
翻訳リンク:
https://www.infoworld.com/art...
もっと高品質の乾物:私のGitHub:csharptranslateを参照