我有MVC项目,服务如下:
namespace comp.Services
{
public class CompService
{
public HttpClient client = new HttpClient();
public CompService()
{
client.BaseAddress = new Uri("someapiurl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
protected async Task<string> GetProductAsync(string path)
{
var resp = "nothing here";
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
resp = await response.Content.ReadAsStringAsync();
}
return resp;
}
public string GetProduct(string path)
{
return GetProductAsync(path).GetAwaiter().GetResult();
}
}
}
和actionResult来查看:
namespace comp.Controllers
{
public class HomeController : Controller
{
public CompService compService;
public HomeController()
{
compService = new CompService();
}
public ActionResult About()
{
var timeServer = compService.GetProduct("/api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
}
在debuger中我遇到这一行:
HttpResponseMessage response = await client.GetAsync(path);
程序从debuger退出,浏览器中没有响应。
在控制台应用程序中编写的相同代码有效。
在VS输出中是响应成功的消息:
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.RemoteDependency","time":"2018-02-18T13:48:31","tags":{"ai.internal.sdkVersion":"rddf:2.2.0-738","ai.internal.nodeName":"DESKTOP-xxxxx","ai.cloud.roleInstance":"DESKTOP-xxxxx"},"data":{"baseType":"RemoteDependencyData","baseData":{"ver":2,"name":"/api/v1/time","id":"xxxxx=","data":"https://api.xxx.com/api/v1/time","duration":"00:00:01.3150000","resultCode":"200","success":true,"type":"Http","target":"xxx","properties":{"DeveloperMode":"true"}}}}
在浏览器控制台输出中:
[14:51:33 GMT+0100 (Central European Standard Time)] Browser Link: Failed to send message to browser link server:
Error: SignalR: Connection must be started before data can be sent. Call .start() before .send()
感谢您的帮助。
答案 0 :(得分:2)
您应该始终保持代码异步,而不是尝试混合同步和异步代码
namespace comp.Services {
public class CompService {
static HttpClient client = new HttpClient();
static CompService() {
client.BaseAddress = new Uri("someapiurl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<string> GetProductAsync(string path) {
var resp = string.Empty;
using(var response = await client.GetAsync(path)) {
if (response.IsSuccessStatusCode) {
resp = await response.Content.ReadAsStringAsync();
}
}
return resp;
}
}
}
控制器操作也应该是async
namespace comp.Controllers {
public class HomeController : Controller {
private CompService compService;
public HomeController() {
compService = new CompService();
}
public async Task<ActionResult> About() {
var timeServer = await compService.GetProductAsync("api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
}
那就是说,服务也应该被抽象
public interface ICompService {
Task<string> GetProductAsync(string path)
}
public class CompService : ICompService {
//...code removed for brevity
}
并注入控制器而不是手动创建。
public class HomeController : Controller {
private ICompService compService;
public HomeController(ICompService compService) {
this.compService = compService;
}
public async Task<ActionResult> About() {
var timeServer = await compService.GetProductAsync("api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
答案 1 :(得分:2)
你得到的是一个僵局。与控制台应用程序不同,ASP.Net应用程序在同步上下文中运行。使用GetResult()
阻止时捕获该上下文。然后,在GetProductAsync
中,等待被阻止的上下文。它在GetResult
完成之前无法恢复,在等待完成之前无法解决。
@NKosi的答案应该可以解决问题,你没有理由拥有任何同步代码。
您可以通过明确允许等待在不同的上下文中运行来 hack 您的代码。你不应该在生产中这样做,这不是一个修复。如果维护CompService
的人不小心,可能会失败。
等待不同的背景改变:
var timeServer = await compService.GetProductAsync("api/time");
对此:
var timeServer = await compService.GetProductAsync("api/time").ConfigureAwait(false);
我之所以提到这一点,只是为了帮助您了解代码中发生的情况。不要以这种方式“修复”并继续前进。