在ASP.Net应用程序中调用异步API

我对ASP.Net和异步编码有点新意,所以请耐心等待。 我在C#中为一个Web API编写了一个异步包装器,我希望在ASP.Net应用程序中使用它。

这是C#API包装器中的一个函数:

public async Task getProducts() { Products products = new Products(); products.data = new List(); string URL = client.BaseAddress + "/catalog/products"; string additionalQuery = "include=images"; HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); if (response.IsSuccessStatusCode) { Products p = await response.Content.ReadAsAsync(); products.data.AddRange(p.data); while (response.IsSuccessStatusCode && p.meta.pagination.links.next != null) { response = await client.GetAsync(URL + p.meta.pagination.links.next + "&" + additionalQuery); if (response.IsSuccessStatusCode) { p = await response.Content.ReadAsAsync(); products.data.AddRange(p.data); } } } return JsonConvert.SerializeObject(products, Formatting.Indented); } 

然后我在我的ASP.Net应用程序中有一个WebMethod(将使用来自Javascript文件的Ajax调用),它应该调用getProducts()函数。

 [WebMethod] public static string GetProducts() { BigCommerceAPI api = getAPI(); return await api.getProducts(); } 

当然,这不起作用,因为WebMethod不是异步方法。 我试图将其更改为异步方法,如下所示:

 [WebMethod] public static async Task GetProducts() { BigCommerceAPI api = getAPI(); return await api.getProducts(); } 

这段代码确实运行了,但是一旦它到达HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); 在getProducts()函数中,调试器将停止,而不会返回任何错误或数据。

我错过了什么? 如何从我的ASP应用程序调用此异步API?

所以我实际上解决了一个与昨晚很相似的问题。 这很奇怪,因为这个电话在.net 4.5中有效。 但是我们移动到4.5.2并且该方法开始死锁。

我在async和asp.net上发现了这些有启发性的文章( 这里 , 这里 , 这里 )。

所以我修改了我的代码

  public async Task GetMemberByOrganizationId(string organizationId) { var task = await // ReSharper disable once UseStringInterpolation _httpClient.GetAsync(string.Format("mdm/rest/api/members/member?accountId={0}", organizationId)).ConfigureAwait(false); task.EnsureSuccessStatusCode(); var payload = task.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(await payload.ConfigureAwait(false), new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); } 

这解决了我的僵局问题。

所以TLDR:来自Stephen Cleary的文章

在概述中,我提到当你等待内置等待时,等待将捕获当前的“上下文”,然后将其应用于异步方法的其余部分。 究竟是什么“背景”?

简单回答:

如果你在UI线程上,那么它就是一个UI上下文。 如果您正在响应ASP.NET请求,那么它是一个ASP.NET请求上下文。 否则,它通常是一个线程池上下文。 复杂的答案:

如果SynchronizationContext.Current不为null,则它是当前的SynchronizationContext。 (UI和ASP.NET请求上下文是SynchronizationContext上下文)。 否则,它是当前的TaskScheduler(TaskScheduler.Default是线程池上下文)。

和解决方案

在这种情况下,您希望通过调用ConfigureAwait并传递false来告知awaiter不捕获当前上下文

我不确定ASP.NET中的[WebMethod]是什么。 我记得它曾经是SOAP Web服务,但是没有人再这样做,因为我们有带控制器的Web API,你可以在动作方法中使用async / await。

测试代码的一种方法是使用.Result同步执行异步方法:

 [WebMethod] public static string GetProducts() { BigCommerceAPI api = getAPI(); return api.getProducts().Result; } 

正如maccettura在评论中指出的那样,它是一个同步调用,它锁定了线程。 要确保没有死锁,请遵循Fran的建议,并在getProducts()方法中的每个异步调用结束时添加.ConfigureAwait(false)

首先按惯例GetProducts()应该命名为GetProductsAsync()

其次, async不会为它的方法调用神奇地分配新线程。 async-await主要是利用自然异步的API,例如对数据库的网络调用或远程Web服务。 使用Task.Run ,您显式使用线程池线程来执行您的委托。

 [WebMethod] public static string GetProductsAsync() { BigCommerceAPI api = getAPI(); return Task.Run(() => api.getProductsAsync().Result); } 

检查此链接这是一个关于如何在ASP.NET中实现异步Web服务调用的项目示例