我几天来一直在和它斗争,无法弄明白或为什么会这样。我希望你们中的一个主谋能够向我解释。
这是有问题的代码:
public async static Task<RemoteFileDetails> GetRemoteFileDetailsAsync(string strUrl, double dblTimeoutSeconds = 15.0)
{
try
{
using (var hc = new HttpClient())
{
hc.Timeout = TimeSpan.FromSeconds(dblTimeoutSeconds);
using (var h = new HttpRequestMessage(HttpMethod.Head, strUrl))
{
h.Headers.UserAgent.ParseAdd(UserAgent);
using (var rm = await hc.SendAsync(h))
{
rm.EnsureSuccessStatusCode();
return new RemoteFileDetails()
{
Url = strUrl,
FileName = rm.Content.Headers.ContentDisposition.FileName,
FileSize = rm.Content.Headers.ContentLength.GetValueOrDefault(),
LastModified = rm.Content.Headers.LastModified.GetValueOrDefault().LocalDateTime,
Valid = true
};
}
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.ToString());
}
return new RemoteFileDetails();
}
所有这一切都是在URL上输入HEAD请求并获取某些标题结果。
这里的问题是大约50%的时间死锁(暂停60秒左右,然后抛出TaskCanceledException
),另外50%它的效果非常好。现在,我添加了网络日志记录,这就是死锁时发生的事情:
System.Net Verbose: 0 : [2928] HttpWebRequest#3567399::HttpWebRequest(https://www.example.com/file.txt#-114720138)
System.Net Information: 0 : [2928] Current OS installation type is 'Client'.
System.Net Information: 0 : [2928] RAS supported: True
System.Net Verbose: 0 : [2928] Exiting HttpWebRequest#3567399::HttpWebRequest()
System.Net Verbose: 0 : [2928] HttpWebRequest#3567399::HttpWebRequest(uri: 'https://www.example.com/file.txt', connectionGroupName: '565144')
System.Net Verbose: 0 : [2928] Exiting HttpWebRequest#3567399::HttpWebRequest()
System.Net Verbose: 0 : [5456] HttpWebRequest#3567399::BeginGetResponse()
System.Net Verbose: 0 : [2244] HttpWebRequest#3567399::Abort()
System.Net Error: 0 : [2244] Exception in HttpWebRequest#3567399:: - The request was aborted: The request was canceled..
/////// THIS IS WHERE THE 30-60 SECOND PAUSE HAPPENS ////////
System.Net Error: 0 : [5456] Can't retrieve proxy settings for Uri 'https://www.example.com/file.txt'. Error code: 12180.
System.Net Verbose: 0 : [5456] ServicePoint#39086322::ServicePoint(www.example.com:443)
System.Net Information: 0 : [5456] Associating HttpWebRequest#3567399 with ServicePoint#39086322
System.Net Verbose: 0 : [2244] Exiting HttpWebRequest#3567399::Abort()
System.Net Verbose: 0 : [5456] HttpWebRequest#3567399::EndGetResponse()
System.Net Error: 0 : [5456] Exception in HttpWebRequest#3567399::EndGetResponse - The request was aborted: The request was canceled..
System.Net Verbose: 0 : [5456] Exiting HttpWebRequest#3567399::BeginGetResponse() -> ContextAwareResult#36181605
那么,看起来HttpClient()对象正在处理?但是怎么样?为了验证可能是这种情况,我将代码更改为:
private static HttpClient _hc;
public async static Task<RemoteFileDetails> GetRemoteFileDetailsAsync(string strUrl, double dblTimeoutSeconds = 15.0)
{
_hc = new HttpClient();
try
{
_hc.Timeout = TimeSpan.FromSeconds(dblTimeoutSeconds);
using (var h = new HttpRequestMessage(HttpMethod.Head, strUrl))
{
h.Headers.UserAgent.ParseAdd(UserAgent);
using (var rm = await _hc.SendAsync(h))
{
rm.EnsureSuccessStatusCode();
return new RemoteFileDetails()
{
Url = strUrl,
FileName = rm.Content.Headers.ContentDisposition.FileName,
FileSize = rm.Content.Headers.ContentLength.GetValueOrDefault(),
LastModified = rm.Content.Headers.LastModified.GetValueOrDefault().LocalDateTime,
Valid = true
};
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.ToString());
}
return new RemoteFileDetails();
}
但它仍然死锁。
有没有解释为什么会发生这种情况?我认为async / await基本上是&#34;暂停&#34;例程,所以无论如何HttpClient都不应该处理。
或者我在这里完全错过了这艘船,而且还有别的东西。
ETA: 我想重要的是要说明我如何称呼这个。这是一个使用MVVM Light的WPF应用程序。在主视图模型中,我这样称呼它:
public MainViewModel(IDataService ds)
{
_dataService = ds;
this.Initialize();
}
private async void Initialize()
{
var det = await RemoteFileUtils.GetRemoteFileDetailsAsync("https://example.com/file.txt");
}
答案 0 :(得分:2)
问题源于如何被调用。
不要在调用类的构造函数中调用它。除非它在事件处理程序中,否则也要避免使用async void
。
public class MainViewModel : ViewModelBase {
public MainViewModel(IDataService ds) {
_dataService = ds;
this.Initialize += OnInitialize;
//Un-comment if you want to call event immediately
//Initialize(this, EventArgs.Empty);
}
public event EventHandler Initialize;
private async void OnInitialize(object sender, EventArgs e) {
var det = await RemoteFileUtils.GetRemoteFileDetailsAsync("https://example.com/file.txt");
}
//Or call this after class has created.
public void Ready() {
Initialize(this, EventArgs.Empty);
}
}
接下来尝试在每次需要发出请求时都不要创建和制作新的HttpClient
。创建一个实例并在整个应用程序的生命周期中使用它。
GetRemoteFileDetailsAsync
也应该被重构为可注射服务,而不是静态访问。