为什么我应该创建异步 WebAPI 操作而不是同步操作?

在我创建的 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);
}

对这个 webservice 的调用是通过一个 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 操作异步对我有益?

85827 次浏览

In your specific example the operation is not asynchronous at all so what you're doing is async over sync. You're just releasing one thread and blocking another. There's no reason to that, because all threads are thread pool threads (unlike in a GUI application).

In my discussion of “async over sync,” I strongly suggested that if you have an API which internally is implemented synchronously, you should not expose an asynchronous counterpart that simply wraps the synchronous method in Task.Run.

From Should I expose asynchronous wrappers for synchronous methods?

However when making WebAPI calls async where there's an actual asynchronous operation (usually I/O) instead of blocking a thread that sits and waits for a result the thread goes back to the thread pool and so able to perform some other operation. Over all that means that your application can do more with less resources and that improves scalability.

One approach could be (I've used this successfully in customer applications) to have a Windows Service running the lengthy operations with worker threads, and then do this in IIS to free up the threads until the blocking operation is complete: Note, this presumes results are stored in a table (rows identified by jobId) and a cleaner process cleaning them up some hours after use.

To answer the question, "Given my problem and context, how will making the webAPI operation asynchronous benefit me?" given that it's "quite a long operation" I'm thinking many seconds rather than ms, this approach frees up IIS threads. Obviously you also have to run a windows service which itself takes resource but this approach could prevent a flood of the slow queries from stealing threads from other parts of the system.

// 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);
}