后台Void类型PayPal校验方法改为Ajax可调用String方法超时失败如何解决
问题根因
- 你用的ASMX是.NET Framework早期的传统Web服务技术栈,原生不支持返回
Task<T>类型的异步WebMethod,Task异步编程模型推出时间远晚于ASMX规范,所以你改造后的异步方法无法被服务正确调度,会出现异步上下文死锁,请求一直挂起直到超时,这就是你设置再大的超时阈值也没用的原因。 - 原来的
async void方法能正常运行是因为属于“发射即遗忘”的执行模式,调用后立刻返回,后台异步执行逻辑不会阻塞请求线程,但也因此无法给前端返回执行结果。 - 你的代码还存在隐藏bug:
nextPayment2.AddMonths(1);这行无效,DateTime是值类型,AddMonths方法会返回新的DateTime对象,你没有接收赋值,导致后续的下次付款时间计算错误。
解决方案
方案1:改为同步实现(最稳妥,兼容性最好)
直接把异步调用逻辑改为同步执行,适配ASMX的执行模型:
[WebMethod] public string CheckAcc() { userManager um = new userManager(); var userID = HttpContext.Current.User.Identity.GetUserId(); um.Get(userID); var subid = um.ppID.Trim(); var token = getToken(); using (var httpClient = new HttpClient()) { using (var request = new HttpRequestMessage(new HttpMethod("GET"), "https://api.paypal.com/v1/billing/subscriptions/" + subid)) { var id = ""; var nextPayment = ""; var status = ""; var payerEmail = ""; var currency = ""; request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + token); // 同步等待异步方法执行完成 var response = httpClient.SendAsync(request).GetAwaiter().GetResult(); string res = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); var obj2 = JsonConvert.DeserializeObject<dynamic>(res); id = obj2.id; nextPayment = obj2.billing_info.next_billing_time; if (nextPayment == null) { nextPayment = obj2.billing_info.last_payment.time; var nextPayment2 = Convert.ToDateTime(nextPayment); // 修复DateTime赋值bug nextPayment2 = nextPayment2.AddMonths(1); nextPayment = nextPayment2.ToString(); } status = obj2.status; status = status.ToLower(); status = FirstLetterToUpper(status); payerEmail = obj2.subscriber.email_address; currency = obj2.shipping_amount.currency_code; um.updatePaypal_noIPN(id, status, "Regular", nextPayment, payerEmail, currency); return status + ", " + payerEmail + ", "; } } }
同步修改前端调用代码,调整接口地址和错误提示逻辑方便排查问题:
$( document ).ready(function() { $.ajax({ type: "POST", url: '/WebServices/checkPayPal.asmx/CheckAcc', cache: false, contentType: "application/json; charset=utf-8", dataType: "json", success: OnSuccess, error: OnError, timeout: 30000 // PayPal接口常规响应时间不会超过30秒,无需设置过大 }); }); function OnSuccess(data, status) { alert(data.d); } function OnError(data, status) { alert("调用失败,错误信息:" + data.responseText); }
方案2:保留异步实现(要求.NET Framework 4.5及以上版本)
如果一定要用异步逻辑,可以修改配置和代码适配:
- 首先在web.config的
system.web节点下添加配置开启异步支持:
<system.web> <httpRuntime targetFramework="4.5" /> <pages async="true" /> </system.web>
- 修改后端异步方法,所有await调用后添加
.ConfigureAwait(false)避免上下文死锁,同时修复DateTime赋值bug:
[WebMethod] public async System.Threading.Tasks.Task<string> CheckAccAsync() { userManager um = new userManager(); var userID = HttpContext.Current.User.Identity.GetUserId(); um.Get(userID); var subid = um.ppID.Trim(); var token = getToken(); using (var httpClient = new HttpClient()) { using (var request = new HttpRequestMessage(new HttpMethod("GET"), "https://api.paypal.com/v1/billing/subscriptions/" + subid)) { var id = ""; var nextPayment = ""; var status = ""; var payerEmail = ""; var currency = ""; request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + token); // 添加ConfigureAwait(false)避免上下文死锁 var response = await httpClient.SendAsync(request).ConfigureAwait(false); string res = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var obj2 = JsonConvert.DeserializeObject<dynamic>(res); id = obj2.id; nextPayment = obj2.billing_info.next_billing_time; if (nextPayment == null) { nextPayment = obj2.billing_info.last_payment.time; var nextPayment2 = Convert.ToDateTime(nextPayment); // 修复DateTime赋值bug nextPayment2 = nextPayment2.AddMonths(1); nextPayment = nextPayment2.ToString(); } status = obj2.status; status = status.ToLower(); status = FirstLetterToUpper(status); payerEmail = obj2.subscriber.email_address; currency = obj2.shipping_amount.currency_code; um.updatePaypal_noIPN(id, status, "Regular", nextPayment, payerEmail, currency); return status + ", "+payerEmail+", "; } } }
内容的提问来源于stack exchange,提问作者MattHodson
相关产品推荐
相关产品推荐

