web-dev-qa-db-ja.com

同期操作ではなく非同期Web API操作を作成する必要があるのはなぜですか?

作成した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()は非常に長い操作であると言わざるを得ません。私の問題とコンテキストを考えると、webAPI操作を非同期にすることはどのように私に利益をもたらしますか?

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

「非同期での同期」の説明で、内部で同期的に実装されるAPIがある場合は、Task.Runで同期メソッドを単純にラップする非同期のカウンターパートを公開しないことを強くお勧めします。

非同期メソッドの同期ラッパーを公開する必要がありますか?

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

94
i3arnon

1つのアプローチとしては(ワーカーアプリケーションでこれを正常に使用しました)、Windowsサービスでワーカースレッドを使用して長時間の操作を実行し、IISでこれを実行して、ブロック操作が完了するまでスレッドを解放しますcomplete:注:これは、結果がテーブル(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);
}
0
user5292841