C#でのValueTaskの使用方法

5044 ワード

は、特にC 5.0に導入されたawait、asyncキーワードがコードの作成をより簡単にし、 を利用してアプリケーションの を高めることができると信じています.
非同期メソッドではTaskを返すことが推奨されていますが、非同期メソッドでいくつかのデータを返す必要がある場合は、TaskTaskに変更することができます.また、あなたの非同期メソッドが何も戻る必要がない場合は、voidに変更することができます.
C#7.0までの非同期メソッドの戻り値は、次の3つです.
  • Task
  • Task
  • void

  • C#7.0以降は、上記の3つの他にValueTaskValueTaskを返すことができます.これらのクラスは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つのメリットを提供します.
  • ValueTaskは値タイプであるため、管理スタックへのメモリ割り当てを回避し、より高いパフォーマンスを実現します.
  • ValueTaskは、より便利で柔軟性があります.

  • 総じて言えば、もしあなたの非同期方法が直接結果を得ることができるならば、TaskValueTaskに変更することを提案して、それによって不要な性能のオーバーヘッドを避けて、TaskValueTaskはすべて待つことができる操作を表して、ここで注意しなければならないのは、決して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を初めて使用するときに、このインスタンスをキャッシュして、後で再多重化することができます.
    最後に をまとめましょう.
  • 非同期メソッドがすぐに完了しない場合は、Taskを使用します.
  • もしあなたの非同期の方法がすぐに完成することができるならば、例えば純粋なメモリの操作、キャッシュを読むならば、ValueTaskを使ってください.

  • いずれにしても、ValueTaskを使う前に必ず必要な性能分析をして、自分に十分な理由を与えてValueTaskを使います.
    翻訳リンク:
    https://www.infoworld.com/art...
    もっと高品質の乾物:私のGitHub:csharptranslateを参照