同期操作の代わりに非同期WebAPI操作を作成する必要があるのはなぜですか?


109

作成したWeb APIに次の操作があります。

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

このWebサービスの呼び出しは、次の方法でJquery Ajax呼び出しを介して行われます。

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

この方法で前の操作を実装する開発者を見てきました。

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

ただし、GetProductsWithHistory()はかなり長い操作であるとGottaは言います。私の問題とコンテキストを考えると、webAPI操作を非同期にすることはどのように役立ちますか?


1
クライアント側は、すでに非同期であるAJAXを使用します。また、サービスをとして記述する必要はありませんasync Task<T>。TPLも:)存在していた前に、AJAXが実装された、覚えておいてください
ドミニクZukiewicz

65
なぜ非同期コントローラーを実装するのかを理解する必要がありますが、多くはそうではありません。IISで使用できるスレッドの数には制限があり、すべてが使用中の場合、サーバーは新しい要求を処理できません。プロセスがI / Oの完了を待機しているときに非同期コントローラーを使用すると、そのスレッドは解放され、サーバーが他の要求の処理に使用できるようになります。
Matija Grcic 2014年

3
あなたはそれをどのような開発者に見ましたか?その手法を推奨するブログ投稿や記事がある場合は、リンクを投稿してください。
Stephen Cleary 2014年

3
プロセスが上から(Webアプリケーション自体とコントローラーを含む)上から非同期に対応し、プロセスの外に出る待機可能なアクティビティ(タイマーの遅延、ファイルI / O、DBアクセスを含む)に至るまで、非同期のメリットを最大限に活用できます。そしてそれが作るウェブリクエスト)。この場合は、デリゲートのヘルパーが必要とGetProductsWithHistoryAsync()返しますTask<CartTotalsDTO>。コントローラが非同期にする呼び出しも移行する場合は、コントローラを非同期で記述することにもメリットがあります。その後、残りの部分を移行するときに、非同期の部分からメリットを得ることができます。
キース・ロバートソン

1
実行しているプロセスがデータベースにアクセスしている場合、Webスレッドはそれが戻るのを待って、そのスレッドを保持しています。最大スレッド数に達し、別のリクエストが来た場合、待機する必要があります。なぜそうするのですか?代わりに、そのスレッドをコントローラーから解放して、別の要求がそれを使用できるようにし、データベースからの元の要求が戻ってきたときにのみ別のWebスレッドを使用するようにします。msdn.microsoft.com/en-us/magazine/dn802603.aspx
user441521

回答:


98

あなたの具体的な例では、操作はまったく非同期ではないので、あなたがしていることは同期よりも非同期です。1つのスレッドを解放し、別のスレッドをブロックしているだけです。すべてのスレッドが(GUIアプリケーションとは異なり)スレッドプールスレッドであるため、その理由はありません。

「非同期オーバーシンク」の説明で、内部的に同期的に実装されているAPIがある場合は、同期メソッドをでラップするだけの非同期対応を公開しないことを強くお勧めしましたTask.Run

万一I非同期メソッドの同期ラッパーを公開?

ただし、async座って結果を待つスレッドをブロックするのではなく、実際の非同期操作(通常はI / O)があるWebAPI呼び出しを行う場合、スレッドはスレッドプールに戻り、他の操作を実行できます。つまり、アプリケーションは少ないリソースで多くのことを実行でき、スケーラビリティが向上します。


3
@efarukすべてのスレッドはワーカースレッドです。あるThreadPoolスレッドを解放して別のスレッドをブロックしても意味がありません。
i3arnon

1
@efarukあなたが何を言おうとしているのかよくわかりません。
i3arnon 2016年

@efaruk "async over sync"(つまりawait Task.Run(() => CPUIntensive()))はasp.netでは役に立たない。それを行っても何も得られません。あるThreadPoolスレッドを解放して別のスレッドを占有しているだけです。単に同期メソッドを呼び出すよりも効率的ではありません。
i3arnon

1
@efarukいいえ、それは合理的ではありません。例では、独立したタスクを順次実行します。推奨を行う前に、asyc / awaitを実際に読む必要があります。await Task.WhenAll並列実行するにはを使用する必要があります。
セーレンBoisen

1
@efaruk Boisenが説明しているように、この例では、これらの同期メソッドを順番に呼び出すだけで値が追加されることはありません。Task.Run複数のスレッドで負荷を並列化したい場合に使用できますが、それは「非同期同期」が意味するものではありません。「async over sync」は、非同期メソッドを同期メソッドのラッパーとして作成することを指します。あなたは私の答えの引用で見ることができます。
i3arnon

1

1つのアプローチは(私はこれを顧客のアプリケーションで正常に使用しました)、Windowsサービスでワーカースレッドを使用して長い操作を実行し、IISでこれを実行して、ブロック操作が完了するまでスレッドを解放します。結果はテーブル(jobIdで識別される行)に格納され、クリーナープロセスは使用後数時間でクリーンアップします。

「私の問題とコンテキストを考えると、webAPIオペレーションを非同期にすることはどのように役立ちますか?」という質問に答えるために。"かなり長い操作"であることを考えると、msではなく何秒か考えているので、このアプローチはIISスレッドを解放します。当然、それ自体がリソースを使用するWindowsサービスも実行する必要がありますが、このアプローチでは、低速なクエリのフラッドによってシステムの他の部分からスレッドが盗まれるのを防ぐことができます。

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.