【问题标题】:Why should I create async WebAPI operations instead of sync ones?为什么我应该创建异步 WebAPI 操作而不是同步操作?
【发布时间】:2014-11-27 07:43:52
【问题描述】:

我在自己创建的 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 操作异步对我有什么好处?

【问题讨论】:

  • 客户端使用AJAX,已经是异步的了。您不需要将该服务也写为async Task&lt;T&gt;。请记住,AJAX 在 TPL 存在之前就已经实现了 :)
  • 您需要了解为什么要实现异步控制器,很多人没有。 IIS 的可用线程数量有限,当所有线程都在使用时,服务器无法处理新请求。使用异步控制器,当进程等待 I/O 完成时,它的线程被释放给服务器用于处理其他请求。
  • 您见过哪些开发人员这样做?如果有任何推荐该技术的博客文章或文章,请发布链接。
  • 只有当您的进程从顶部(包括 Web 应用程序本身和您的控制器)到任何超出您的进程的可等待活动(包括计时器延迟、文件 I/O、数据库访问和它发出的 Web 请求)。在这种情况下,您的委托助手需要返回 Task&lt;CartTotalsDTO&gt;GetProductsWithHistoryAsync()。如果您也打算将控制器的调用迁移为异步,那么编写异步控制器可能会有好处;然后在迁移其余部分时开始从异步部分中受益。
  • 如果您正在执行的进程正在关闭并访问数据库,那么您的 Web 线程只是在等待它返回并持有该线程。如果你已经达到了最大线程数并且另一个请求进来了,它必须等待。为什么要这样做?相反,您希望从您的控制器中释放该线程,以便另一个请求可以使用它,并且仅在您来自数据库的原始请求返回时才占用另一个 Web 线程。 msdn.microsoft.com/en-us/magazine/dn802603.aspx

标签: c# jquery ajax asp.net-web-api async-await


【解决方案1】:

在您的具体示例中,操作根本不是异步的,因此您所做的是异步而不是同步。你只是释放一个线程并阻塞另一个线程。没有理由这样做,因为所有线程都是线程池线程(与 GUI 应用程序不同)。

在我对“异步优于同步”的讨论中,我强烈建议如果您有一个内部同步实现的 API,您不应该公开一个简单地将同步方法包装在 Task.Run 中的异步对应物。

来自Should I expose asynchronous wrappers for synchronous methods?

但是,当进行 WebAPI 调用 async 时,其中有一个实际的异步操作(通常是 I/O),而不是阻塞一个等待结果的线程,该线程返回到线程池,因此能够执行一些其他操作手术。总而言之,这意味着您的应用程序可以用更少的资源做更多的事情并提高可扩展性。

【讨论】:

  • @efaruk 所有线程都是工作线程。释放一个 ThreadPool 线程并阻塞另一个线程是没有意义的。
  • @efaruk 我不确定你想说什么.. 但只要你同意在 WebAPI 中没有理由使用异步而不是同步就可以了。
  • @efaruk "async over sync"(即await Task.Run(() =&gt; CPUIntensive()))在 asp.net 中没有用。你这样做不会有任何收获。您只是释放一个 ThreadPool 线程来占用另一个线程。它比简单地调用同步方法效率低。
  • @efaruk 不,这不合理。您的示例按顺序运行独立任务。在提出建议之前,您确实需要阅读 asyc/await。您需要使用await Task.WhenAll 才能并行执行。
  • @efaruk 正如 Boisen 解释的那样,除了简单地按顺序调用这些同步方法之外,您的示例并没有增加任何价值。如果你想在多个线程上并行化你的负载,你可以使用Task.Run,但这不是“异步超过同步”的意思。 “async over sync”指的是创建一个异步方法作为同步方法的包装器。您可以在我的回答中的引用中看到。
【解决方案2】:

一种方法可能是(我已在客户应用程序中成功使用此方法)让 Windows 服务使用工作线程运行冗长的操作,然后在 IIS 中执行此操作以释放线程,直到阻塞操作完成: 请注意,这假定结果存储在一个表中(由 jobId 标识的行),并且一个更清洁的进程在使用后数小时清理它们。

回答这个问题,“考虑到我的问题和背景,使 webAPI 操作异步对我有什么好处?”考虑到它是“相当长的操作”,我在想很多秒而不是毫秒,这种方法可以释放 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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-02
    • 1970-01-01
    • 2023-03-16
    • 2016-08-11
    相关资源
    最近更新 更多