为什么要创建异步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);
}

通过以下方式通过Jquery Ajax调用完成对此Web服务的调用:

$.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
客户端使用已经异步的AJAX。您不需要将服务也写为async Task<T>。记住,在TPL甚至还没有存在之前就已经实现了AJAX :)
Dominic Zukiewicz 2014年

65
您需要了解为什么要实现异步控制器,而许多人却没有。IIS具有有限数量的可用线程,当所有线程都在使用中时,服务器将无法处理新请求。使用异步控制器时,当一个进程在等待I / O完成时,它的线程被释放给服务器以用于处理其他请求。
Matija Grcic 2014年

3
您看到过哪些开发人员?如果有任何博客文章或文章推荐该技术,请发布链接。
Stephen Cleary 2014年

3
仅当您的流程从顶部(包括Web应用程序本身和控制器)开始感知到流程之外的任何可等待活动(包括计时器延迟,文件I / O,数据库访问,和网络发出的请求)。在这种情况下,您的委托助手需要一个GetProductsWithHistoryAsync()returning Task<CartTotalsDTO>。如果您打算迁移控制器发出的异步调用,则将控制器编写为异步也会有好处;然后,您将在迁移其余部分时从异步部分中受益。
基思·罗伯逊

1
如果您正在执行的过程正在关闭并访问数据库,则您的Web线程只是在等待它返回并保持该线程。如果您达到最大线程数,并且另一个请求进入,则必须等待。为什么这样 相反,您希望从控制器中释放该线程,以便另一个请求可以使用它,并且仅在您的数据库原始请求返回时占用另一个Web线程。msdn.microsoft.com/zh-CN/magazine/dn802603.aspx
2016年

Answers:


98

在您的特定示例中,该操作根本不是异步的,因此您要做的是异步同步。您只是释放一个线程而阻塞另一个线程。不需要这样做,因为所有线程都是线程池线程(与GUI应用程序不同)。

在关于“异步同步”的讨论中,我强烈建议,如果您有一个内部实现了同步的API,则不应公开仅将同步方法包装在中的异步对象Task.Run

我应该公开异步方法的同步包装器吗?

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


3
@efaruk所有线程都是工作线程。释放一个ThreadPool线程并阻塞另一个线程是没有意义的。
i3arnon

1
@efaruk我不确定您要说的是什么..但是只要您同意,就没有理由在WebAPI中使用异步过同步,那就很好了。
i3arnon

@efaruk“异步同步”(即await Task.Run(() => CPUIntensive()))在asp.net中没有用。您这样做不会获得任何收益。您只是释放一个ThreadPool线程以占用另一个线程。比简单地调用同步方法效率低。
i3arnon

1
@efaruk不,那是不合理的。您的示例按顺序运行独立任务。在提出建议之前,您确实需要先阅读asyc / await。您将需要使用await Task.WhenAll以便并行执行。
索伦Boisen

1
@efaruk正如Boisen解释的那样,您的示例除了简单地依次调用这些同步方法之外,没有添加任何值。Task.Run如果要并行化多个线程上的负载,则可以使用,但这不是“异步同步”的意思。“异步同步”引用创建异步方法作为同步方法的包装。您可以在我的答案中的报价中看到。
i3arnon

1

一种方法可能是(我已在客户应用程序中成功使用过此方法)让Windows Service运行带有辅助线程的冗长操作,然后在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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.