asyncを解読して、C


あなたは2つのキーワードに遭遇したかもしれないasync and await 何度も働いている間.NETプロジェクト.それは使用する魅力的なものですが、それはまた、そのようなコードを扱うときにフードの下で何が起こるか知ることに有益でしょう.
何が何であるかについて徹底的な詳細で詰まっている前に、我々はC .

🤔 タスク対スレッド


簡単に言えば、タスクはスレッドではありません.あなたが働いたならばPromises JavaScriptのような他の言語では、それらを使用して非常に快適になります.タスクは本質的に約束されます.そして、それはあなたが非同期動作から返された結果を扱うことができる後の時点で完了します.これはまた、タスクが失敗し、完了したかどうかを知るために問い合わせることができることを意味します.一方、スレッドはOSレベルのコード実行のより低いレベルの実装です.
タスクがスレッド上で抽象化されていないことを理解することは、本当に重要です.そして、タスクを非同期に実行することを意図したいくつかの仕事の抽象化としてタスクを考えるべきです.要約
  • タスクはスレッドではない
  • タスクは、ASAPIコードを処理するクリーンAPIを提供する約束に似ています
  • タスクは並列実行を保証しません
  • タスクを明示的にタスクを介して別のスレッドで実行するように要求することができます.APIを実行します.
  • タスクはTaskSchedulerによって実行される予定です
  • 🔮 「待つ」キーワードの魔法


    ASStephen Cleary 彼の優れたブログ記事ではっきりと言及して、asyncキーワードだけは待ちます.したがって、Asyncメソッドは単にwaitキーワードを見るまで他の同期メソッドと同様に実行されます.
    WAITキーワードは魔法が起こるところです.それは待機して実行されるメソッドの発信者にバックコントロールを与えます、そして、最終的にIOバインド(例えば、Webサービスを呼ぶこと)またはCPU束縛された仕事(例えば、CPU重い計算のような)は反応するのを許します.すべてのasyncとwaitはきれいなコードを書くためにいくつかの良い構文の砂糖を提供しています.
    以下の簡単な例を考えてみましょう.
    public async Task<string> DownloadString(string url)
    {
       var client = new HttpClient();
       var request = await client.GetAsync(url);
       var download = await request.Content.ReadAsStringAsync();
       return download;
    }
    
    同じコードは、フードの下に展開されるものと等価です.
    public Task<String> DownloadString(string url) 
    { 
        var client = new HttpClient();
        var request = client.GetAsync(url); 
        var download = request.ContinueWith(http => 
            http.Result.Content.ReadAsStringAsync()); 
        return download.Unwrap(); 
    }
    
    ご覧のように、TASKスケジューラでキューを実行してキューに入れる必要のある一連のタスクを作成することができます.上記のコードはいくぶんOKです、しかし、手動ですべてをすることなく、C .
    この方法が実行される手順は次のとおりです.
  • 呼び出しclient.GetAsync(url) 下位レベルを呼び出すことによって舞台裏でリクエストを作成します.NETライブラリ.
  • その背後にあるコードの一部は、ネットワークAPIからOSへの作業を委ねるまで同期的に実行されるかもしれません
  • この時点で、タスクが作成され、非同期コードの元の呼び出し元にバブルブルされます.これはまだ未完成のタスクです!
  • この時間の間、呼び出し元はタスクの状態を問い合わせることができる
  • 一旦ネットワーク・リクエストがOSレベルによって完了されるならば、応答はIO完成ポートを通して返されます、そして、CLRは仕事がされるCPU割込みによって通知されます
  • 応答は、データを展開するために次の利用可能なスレッドによって処理される予定です
  • あなたのasyncメソッドの残りは同期して走り続けます
  • ここでのキーテイクアウトは、タスクを完了するために専用のスレッドが存在しないことです.これは、あなたのタスクの継続/完了がそれを開始した同じスレッドで実行することが保証されていないことを意味します.

    ☠️ 一般的なgotchasときにasyncを使用して待つ


    使用を開始すると、コード内のすべてを「非同期」にするように誘惑されますasync and await (さて)😁). しかし、あなたのコードで避けるべきである落とし穴があります.
  • メソッドのvoidメソッドの作成
  • これは私が私の手を非同期で待っていたときに最初のミスです!非同期メソッドと呼ばれるvoidメソッドがありました.呼び出しメソッドを非同期にすることなく待機することができないので、呼び出し元メソッドasync voidを作りました!
    public async void GetResult() 
    {
        await SomeAsyncMethod();
    }
    
    ここでの問題は、getResultメソッドの呼び出し元がSomeAsyncMethod() これはまた、何かがうまくいかない場合、呼び出しスタックの不足などの他の副作用を引き起こします.また、例外が発生した場合にアプリケーションクラッシュします.
    しかし、あなたがこれを避けることができない時間が、あります.あなたが我々のコードでAsyncWPFAPPを開くならばexample 次のようなコードが見つかります.
    private async void AsyncParallelBtn_Click(object sender, RoutedEventArgs e)
    {
        ...
        var output = await RunAsyncParallelDemo.Start();
        ...
    }
    
    WPFのイベントハンドラのメソッドシグネチャを変更することができないため、上記のようなコードを見つけた場合、必ずしも問題ではありません.

    Nevertheless, it’s recommended to avoid using async void where possible.

  • 無視するか忘れるのを忘れる
  • コンパイラが不満なくコードをコンパイルするので、非同期タスクで待つのを忘れるのは簡単な間違いです.以下の例では、単にasync関数をawait それについていけ.
    public void Caller()
    {
        Console.WriteLine("Before");
    
        // Deliberately forgetting to await
        DoSomeBackgroundWorkAsync();
    
        Console.WriteLine("After");
    }
    
    DoSomeBackgroundWorkAsync() メソッドは、実際に実行せずに呼び出し元メソッドにタスク(タスクに応じたタスクまたはタスク)を返します.

    Therefore, be mindful when you are dealing with async methods (especially with third party libraries) not to forget to use await

  • を使ってタスクをブロックします.結果、そして.wait ()
  • これは別の一般的なトラップの開発者が😋) 同期メソッドでいくつかのasyncコードを実行する必要があるとき.以下の例を考えてみましょう.
    public void DoSomeWork()
    {
        var result = DoAsyncWork().Result;
    }
    
    一目で、それを使用するのはかなり便利かもしれません.Result プロパティ.しかし、これは深刻な原因deadlock problems . これを避けるために、通常、呼び出し元のメソッドを非同期にする必要があります.これはあなたの変更にカスケード効果を作成すると、多くの仕事かもしれません.しかし、それは通常好ましいです.
    MSDNで明確に示されているような非ブロッキングコードを書く方法

    It’s recommended to make the caller async where you would end up making a lot of cascading changes to your codebase although it could be painful if you are working on a legacy project.


    結論


    私は、あなたが私がそれを書いたのと同じくらいこの記事を楽しんだことを望みます.一番下の行は、タスクはほとんど常に最良のオプションですそれははるかに強力なAPIを提供し、OSのスレッドを無駄に回避します.
    より多くのもののための準備?私のgithubレポにいくつかのサンプルコードを介して動作するように頭を.
    https://github.com/sahan91/c-sharp-tasks

    提案やバグを発見?


    あなたが私が説明したものの中で誤解があると思うならば、下記のコメントを自由にしてください.乾杯!

    参考文献

  • https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl
  • https://blog.stephencleary.com/2012/02/async-and-await.html
  • https://markheath.net/post/async-antipatterns
  • https://msdn.microsoft.com/en-us/magazine/jj991977.aspx