【问题标题】:Calling Asynchronous API in ASP.Net Application在 ASP.Net 应用程序中调用异步 API
【发布时间】:2020-06-20 01:25:21
【问题描述】:

我对 ASP.Net 和异步编码有点陌生,所以请耐心等待。我已经用 C# 为我想在 ASP.Net 应用程序中使用的 Web API 编写了一个异步包装器。

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

public async Task<string> getProducts()
{
    Products products = new Products();
    products.data = new List<Item>();

    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>();
        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>();
                products.data.AddRange(p.data);
            }
        }
    }
    return JsonConvert.SerializeObject(products, Formatting.Indented);
}

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

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

现在这当然行不通,因为 WebMethod 不是异步方法。我试图将其更改为如下所示的异步方法:

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

此代码确实可以运行,但一旦到达 getProducts() 函数中的 HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); 行,调试器就会停止,不会返回任何错误或数据。

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

【问题讨论】:

  • 据我所知,您不需要再次传递基地址,如果您只需执行await client.GetAsync("/catalog/products?" + additionalQuery) 应该可以工作(因为您已经设置了基地址)
  • 所以它仍然挂在那条线上等待激活?
  • @maccettura 在大多数情况下你是正确的,但是在这种情况下,基地址是 http://website.com/store/v3/ 所以如果我只是调用 client.GetAsync("/catalog/products?" + additionalQuery' 它会将请求更改为 http://website.com/catalog/products 缺少 @ 987654329@ 部分,因此任何请求都会返回 404。
  • @Fran 它本身并没有挂起,调试只是停止而没有错误,并且 getProducts() 函数的其余部分没有被调用。 WebMethod 也不返回任何数据。
  • 尝试将 .ConfigureAwait(false) 添加到您正在等待的所有 GetAsync 语句中。如果可行,我会用链接和解释写一个更长的答案。

标签: c# asp.net asynchronous


【解决方案1】:

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

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

所以我把我的代码修改成了这个

    public async Task<Member> 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<Member>(await payload.ConfigureAwait(false),
            new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
    }

解决了我的死锁问题。

所以 TLDR:来自 Stephen Cleary 的文章

在概述中,我提到当您等待内置的可等待对象时, 然后等待的将捕获当前的“上下文”并稍后应用 它到异步方法的其余部分。那究竟是什么 “上下文”?

简单回答:

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

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

解决办法

在这种情况下,您想告诉等待者不要捕获当前 通过调用 ConfigureAwait 并传递 false 的上下文

【讨论】:

  • 效果很好,谢谢!我还必须将 .ConfigAwait(false) 添加到 WebMethod 内的 api.getProducts() 调用中,但这很简单。
  • 天哪,在回答问题时,这些消息来源已经超过 4 年了。堆栈溢出可以使用在这一点上将答案标记为过时的功能。在这一点上,这些信息已经有将近十年的历史了,围绕异步和线程的语言特性和库已经发生了巨大的变化。
【解决方案2】:

我不确定 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)

【讨论】:

  • 当然会。这是一个同步调用。一旦这可行,OP就可以开始考虑使其异步。但我不确定[WebMethod] 服务是否支持它。
  • [WebMethod] 是必需的,以便我可以从 JQuery Ajax 调用中调用此函数。我测试了你的解决方案,但程序仍然在HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); 线上“停止”,所以永远不会调用return api.getProducts().Result;
  • @ConorWatson 你知道你只是说它实际上被调用了。看起来你有一些僵局。正如我在答案中所说,添加.ConfigureAwait(false)。还可以考虑使用现代 Web API 服务,而不是您现在使用的传统服务。
  • @Andrei 您会推荐哪些现代 Web API 服务?
  • @ConorWatson 它被称为 ASP.NET Web API。这是给你的链接:asp.net/web-api
【解决方案3】:

首先按照约定,GetProducts() 应命名为 GetProductsAsync()

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

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

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

【讨论】:

  • 感谢您的回答。现在通过HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); 行没有问题,但随后在ReadAsAsync&lt;Products&gt;() 调用上再次停止。有什么想法吗?
  • 您能否提供有关错误/异常/堆栈跟踪的更多详细信息。您也可以接受答案,因为它解决了您发布的原始问题,因此其他用户可能会知道如何处理这个问题。
  • p = 等待 response.Content.ReadAsAsync().Result;
【解决方案4】:

我有一个非常相似的问题:

  1. 主 webapp 是一个 ASP.NET 4.5 Web 表单,但它的许多功能实现为从 UI 到 aspx.cs 代码隐藏中的 [webMethod] 修饰函数的 AJAX 调用:
  2. web 方法对代理进行异步调用。这个电话是 最初是用 Task.Run() 实现的,我试图用重写 只是等待......
[WebMethod]
public static async Task<OperationResponse<CandidatesContainer>> GetCandidates(string currentRoleName, string customerNameFilter, string countryFilter, string currentQuarter)
{
            string htmlResult = String.Empty;
            List<CandidateEntryDTO> entries = new List<CandidateEntryDTO>();
            try
            {
                entries = await GetCandiatesFromProxy(currentUser, currentRoleName, customerNameFilter, countryFilter, currentQuarter)
                    .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                log.Error("Error .....", ex);
            }
            CandidatesContainer payloadContainer = new CandidatesContainer { 
               CountryMappedCandiates = ..., 
               GridsHtml = htmlResult };
            return new OperationResponse<CandidatesContainer>(payloadContainer, true);
}

3) 调用 GetCandiatesFromProxy(...) 是几个异步方法链的顶部,底部最后是一个 HttpClient.GetAsync(...) 调用:

private async Task<B2PSResponse<string>> GetResponseFromB2PService(string serviceURI)
{
            string jsonResultString = String.Empty;

            if (_httpClientHandler == null)
            {
                _httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
            }
            if (_client == null)
            {
                _client = new HttpClient(_httpClientHandler);
            }
            HttpResponseMessage response = await _client.GetAsync(serviceURI).ConfigureAwait(false);
            HttpContent content = response.Content;
            string json = String.Empty;
            if (response.StatusCode == HttpStatusCode.OK)
            {
                json = await content.ReadAsStringAsync().ConfigureAwait(false);
            }
            B2PSResponse<string> b2psResponse = new B2PSResponse<string>(response.StatusCode, response.ReasonPhrase, json);
            return b2psResponse;
}
  1. 代码不工作(卡在最低级别等待)直到 我开始在每个 await 调用中添加 .ConfigureAwait(false)。
  2. 有趣的是,我不得不将这些 .ConfigureAwait(false) 添加到链上的所有等待调用中 - 一直到 webMethod 中的顶部调用。删除它们中的任何一个都会破坏代码 - 它会在没有 .ConfigureAwait(false) 的等待之后挂起。
  3. 最后一点:我必须修改 Ajax 调用的 SUCCESS 路径。 webmethods 的默认 Jason 序列化使发送到 AJAX 调用的结果为
{data.d.MyObject}
  • 即插入包含实际负载的 {d} 字段。在 webmethod 返回值从 MyObject 更改为 Task 之后 - 这不再有效 - 在 {data.d} 中找不到我的有效负载。结果现在包含
{data.d.Result.MyObject} 

这只是对具有 .Result 字段的 Task 对象进行序列化的结果。 只需对 AJAX 调用进行一点小改动,现在就可以正常工作了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 2020-05-05
    • 1970-01-01
    • 2022-01-19
    相关资源
    最近更新 更多