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

C# 在ASP.Net应用程序中调用异步API,c#,asp.net,asynchronous,C#,Asp.net,Asynchronous,我对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.Bas

我对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);
}
当然,这不会起作用,因为WebMethod不是异步方法。我尝试将其更改为异步方法,如下所示:

[WebMethod]
public static async Task<string> GetProducts()
{
    BigCommerceAPI api = getAPI();
    return await api.getProducts();
}
此代码确实会运行,但一旦它到达HttpResponseMessage响应=wait client.GetAsyncURL+?+附加查询;在getProducts函数中,调试器将停止,并且不会返回任何错误或数据

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

我不确定ASP.NET中的[WebMethod]是什么。我记得它曾经是soapweb服务,但现在没有人再这样做了,因为我们有带控制器的web API,您可以在其中使用async/await-in-action方法

测试代码的一种方法是使用同步执行异步方法。结果:

正如maccettura在评论中指出的,这是一个同步调用,它锁定线程。为了确保没有死锁,请按照Fran的建议,在getProducts方法中的每个异步调用结束时添加.configureWaitFalse。

首先,按照约定,getProducts应命名为GetProductsAsync

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

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

这是一个关于如何在ASP.NET中实现异步web服务调用的项目示例,所以我昨晚解决了一个非常类似的问题。这很奇怪,因为调用在.NET4.5中起作用。但是我们转到4.5.2,方法开始死锁

我在async和asp.net上找到了这些启发性的文章

所以我修改了我的代码

    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请求上下文是 同步上下文上下文。否则,它就是电流 TaskScheduler TaskScheduler。默认为线程池上下文

解决方案呢

在这种情况下,您希望告诉等待者不要捕获当前数据 通过调用ConfigureWait并传递false来创建上下文


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

Main webapp是一个ASP.NET 4.5 Web窗体,但它的许多功能都是通过AJAX调用实现的,从UI调用[webMethod]修饰函数,并在aspx.cs代码中隐藏: webmethod对代理进行异步调用。这个电话是 最初是用Task.Run实现的,我试图用 只是等待。。。 i、 e.插入包含实际有效负载的{d}字段。webmethod返回值从MyObject更改为Task后(这不再有效),在{data.d}中找不到我的有效负载。结果现在包含 这只是序列化任务对象的结果,该对象具有.result字段。
对AJAX调用进行了一个小的更改,现在可以使用了。

据我所知,如果您只是等待client.GetAsync/catalog/products,则无需再次传入基址由于您已经设置了基地址,所以它挂在该行上,仍在等待激活,因此应该可以使用其他查询?@maccettura您在大多数情况下都是正确的,但是在本例中,基地址是http://website.com/store/v3/ 如果我只调用client.GetAsync/catalog/products?+additionalQuery'它将请求更改为http://website.com/catalog/products 它缺少store/v3部分,因此任何请求都返回404。@Fran它本身没有挂起,调试只是停止而没有错误,getProducts函数的其余部分不会被调用。WebMethod也不返回任何数据。请尝试将.ConfigureWaitFalse添加到所有等待的GetAsync语句中。如果这行得通的话,我会写一个较长的答案,并附上链接和解释。当然会。这是一个同步呼叫。一旦这样做了,OP就可以开始考虑使其异步。但是我不确定[WebMethod]服务是否支持它。我需要[WebMethod],这样我就可以通过JQuery Ajax调用来调用这个函数。我测试了你的解决方案和公关
gram仍然在httpresponsemessageresponse=wait client.GetAsyncURL+?+行上“停止”附加查询;因此返回api.getProducts.Result;“从来没有叫过它。”科诺沃森你知道你只是说它实际上被叫做。看起来你有一些僵局。正如我在回答中所说的那样,Add.configurewaitfalse。也可以考虑使用现代Web API服务代替你现在使用的遗留物。@安德列,你会推荐什么样的现代Web API服务?@ CONORWATSON,它被称为ASP.NET Web API。这里有一个链接给你:谢谢你的回答。现在,它将传递HttpResponseMessage response=wait client.GetAsyncURL+?+附加查询;线路没有问题,但在ReadAsAsync调用时再次停止。有什么想法吗?你能提供更多关于错误/异常/堆栈跟踪的细节吗。你也可以接受这个答案,因为它解决了你发布的原始问题,所以其他用户可能会得到如何处理这个问题的线索。p=wait response.Content.ReadAsAsync.Result;非常好,谢谢!我还必须在WebMethod内的api.getProducts调用中添加.ConfigAwaitfalse,但这非常简单。
[WebMethod]
public static string GetProductsAsync()
{
    BigCommerceAPI api = getAPI();
    return Task.Run(() => api.getProductsAsync().Result);
}
    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() });
    }
[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);
}

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;
}
{data.d.MyObject}
{data.d.Result.MyObject}