C# Xamarin 没有收到来自 HttpClient 的响应
C# Xamarin does not receive response from HttpClient
我在 C# Xamarin 中使用以下方法进行身份验证:
private async Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(this._AuthToken))
{
request.Headers.Add("Authorization", this._AuthToken);
}
return await this._Client.SendAsync(request);
}
服务器收到请求,认证成功(出现在服务器日志中)。基于wireshark,很多响应从服务器返回,但函数没有返回。应用程序冻结。
有什么想法吗?
此方法在普通控制台客户端上运行没有任何问题。
没有错误消息,只是一个冻结的客户端。
感谢您的回答!
您在这里遇到的是典型的死锁案例。在评论中,我建议简单地删除 async
和 await
关键字,并简单地 returning Task
和 HttpClient.SendAsync
方法 returns。为什么这会改变什么?让我解释一下。
就在您 await
捕获当前 SynchronizationContext
异步方法之前。在你的情况下,它可能是 UI 线程。
然后当实际的 SendAsync
方法完成其工作时,它将尝试 return 到那个 SynchronizationContext
。这通常没问题。但是,这实际上取决于您实际调用封装方法的方式。
如果您从 UI 线程执行类似以下操作:_SendAsync(..).Result
,这很可能会导致死锁。由于 .Result
是阻塞调用,因此 UI 线程将等待 .Result
完成。由于 UI 线程正在等待,在等待的 SendAsync
方法完成后 return 转到 UI 线程 SynchronizationContext
将不起作用,因为 UI线程被阻塞。
那么在这种情况下简单地 returning 任务有什么用呢?由于您没有等待 Task 并且该方法未标记为异步,因此上下文切换的责任已移至堆栈中调用 _SendAsync
的位置。在这里调用 .Result
不会进行上下文切换,而只会进行阻塞调用并失去开始时执行 async/await 的所有好处。
或者,您也可以将 .ConfigureAwait(false)
添加到等待的 this._Client.SendAsync()
呼叫中。这是做什么的,不是 return 到捕获 SynchronizationContext
而是 return 到它执行的任何 ThreadPool 线程。
在任何一种情况下,使用 .Result
或 .GetAwaiter().GetResult()
时都要非常小心,在大多数情况下应该避免使用。
通常应等待任务。或者,如果您真的不关心结果,您可以使用 Task.Run()
.
解雇并忘记
大多数不需要 return 到捕获的上下文的服务调用和异步调用,大部分可以 post-固定为 ConfigureAwait(false)
。没有它的主要原因是如果你想在之后更新 UI。
您可以阅读更多关于 ConfigureAwait
in the FAQ by Stepen Toub
我强烈建议您阅读有关 async/await in the MS Docs
的优秀文档
长话短说:
要么:
private async Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(this._AuthToken))
{
request.Headers.Add("Authorization", this._AuthToken);
}
return await this._Client.SendAsync(request).ConfigureAwait(false);
}
或者:
private Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(this._AuthToken))
{
request.Headers.Add("Authorization", this._AuthToken);
}
return this._Client.SendAsync(request);
}
解决问题。
并避免在 Tasks 上调用 .Result
。永远等待他们。
我在 C# Xamarin 中使用以下方法进行身份验证:
private async Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(this._AuthToken))
{
request.Headers.Add("Authorization", this._AuthToken);
}
return await this._Client.SendAsync(request);
}
服务器收到请求,认证成功(出现在服务器日志中)。基于wireshark,很多响应从服务器返回,但函数没有返回。应用程序冻结。
有什么想法吗? 此方法在普通控制台客户端上运行没有任何问题。
没有错误消息,只是一个冻结的客户端。
感谢您的回答!
您在这里遇到的是典型的死锁案例。在评论中,我建议简单地删除 async
和 await
关键字,并简单地 returning Task
和 HttpClient.SendAsync
方法 returns。为什么这会改变什么?让我解释一下。
就在您 await
捕获当前 SynchronizationContext
异步方法之前。在你的情况下,它可能是 UI 线程。
然后当实际的 SendAsync
方法完成其工作时,它将尝试 return 到那个 SynchronizationContext
。这通常没问题。但是,这实际上取决于您实际调用封装方法的方式。
如果您从 UI 线程执行类似以下操作:_SendAsync(..).Result
,这很可能会导致死锁。由于 .Result
是阻塞调用,因此 UI 线程将等待 .Result
完成。由于 UI 线程正在等待,在等待的 SendAsync
方法完成后 return 转到 UI 线程 SynchronizationContext
将不起作用,因为 UI线程被阻塞。
那么在这种情况下简单地 returning 任务有什么用呢?由于您没有等待 Task 并且该方法未标记为异步,因此上下文切换的责任已移至堆栈中调用 _SendAsync
的位置。在这里调用 .Result
不会进行上下文切换,而只会进行阻塞调用并失去开始时执行 async/await 的所有好处。
或者,您也可以将 .ConfigureAwait(false)
添加到等待的 this._Client.SendAsync()
呼叫中。这是做什么的,不是 return 到捕获 SynchronizationContext
而是 return 到它执行的任何 ThreadPool 线程。
在任何一种情况下,使用 .Result
或 .GetAwaiter().GetResult()
时都要非常小心,在大多数情况下应该避免使用。
通常应等待任务。或者,如果您真的不关心结果,您可以使用 Task.Run()
.
大多数不需要 return 到捕获的上下文的服务调用和异步调用,大部分可以 post-固定为 ConfigureAwait(false)
。没有它的主要原因是如果你想在之后更新 UI。
您可以阅读更多关于 ConfigureAwait
in the FAQ by Stepen Toub
我强烈建议您阅读有关 async/await in the MS Docs
的优秀文档长话短说:
要么:
private async Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(this._AuthToken))
{
request.Headers.Add("Authorization", this._AuthToken);
}
return await this._Client.SendAsync(request).ConfigureAwait(false);
}
或者:
private Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(this._AuthToken))
{
request.Headers.Add("Authorization", this._AuthToken);
}
return this._Client.SendAsync(request);
}
解决问题。
并避免在 Tasks 上调用 .Result
。永远等待他们。