【发布时间】:2015-08-25 16:04:54
【问题描述】:
我在 IIS 中托管的 Web 服务中遇到了启动问题。该服务需要通过 http online 获取外部资源,以便在服务请求时使用。为了简化这一点,这些资源被收集在后台线程中并被缓存。而且由于我不希望对服务的初始请求失败,因此这些资源在启动期间会立即收集。它们是使用 System.Net.WebClient 类下载的,结果是创建了一堆线程,它们都有一个 WebClient 对象,用于下载资源。
这表现得很奇怪。就好像多个请求以某种方式互相阻塞一样,因为发生的情况是所有请求都需要很长时间才能完成。当所有这些请求都在运行时,我可以调用一个本地托管的网站,该网站由一个小的“hello world”http 页面组成,这将需要几秒钟和几秒钟(使用我当前的设置,如果在后台线程之前调用它需要 0.1 秒是启动,如果在启动后调用,则为 30)。大约一分钟后,一切恢复正常,而不是无法访问任何资源,它可以在一秒钟内突然下载大部分资源。
实验表明,这不是并发线程或连接限制的问题(一分钟内根本没有任何成功的调用 - 如果存在某种连接限制,您会期望一些调用成功) .另一个有趣的实验是,当我尝试使用纯套接字连接到小型本地站点时 - 这工作完美无缺,所以这似乎是 WebClient 类特有的问题。
用于建立连接的类如下所示。对 TimeoutRequestHandler 的调用非常简单 - 您可以使用 uri 和 CustomWebClient 创建它,然后调用 process request。
private class CustomWebClient : WebClient {
public bool KeepAlive { get; set; }
public X509Certificate ClientCertificate { get; set; }
protected override WebRequest GetWebRequest(Uri address) {
WebRequest answer = base.GetWebRequest(address);
HttpWebRequest httpReq = answer as HttpWebRequest;
if (httpReq != null) {
httpReq.KeepAlive = KeepAlive;
if (ClientCertificate != null) {
httpReq.ClientCertificates.Add(ClientCertificate);
}
}
return answer;
}
}
private class TimeoutRequestHandler {
private readonly Uri address;
private readonly WebClient client;
private readonly byte[] requestData;
private readonly TimeSpan timeout;
private readonly object sync;
private ManualResetEvent requestDoneSignal;
private AsyncCompletedEventArgs completedInfo;
public TimeoutRequestHandler(Uri address, WebClient client, byte[] requestData, TimeSpan timeout) {
this.address = address;
this.client = client;
this.requestData = requestData;
this.timeout = timeout;
sync = new object();
requestDoneSignal = new ManualResetEvent(false);
client.UploadDataCompleted += OnRequestCompleted;
client.DownloadDataCompleted += OnRequestCompleted;
}
public byte[] ProcessRequest() {
bool shouldCancel = false;
try {
if (requestData != null) {
client.UploadDataAsync(address, requestData); // Uses POST for HTTP.
} else {
client.DownloadDataAsync(address); // Uses GET for HTTP
}
if (!requestDoneSignal.WaitOne(timeout)) {
shouldCancel = true;
throw new WebException("The operation has timed out");
}
if (completedInfo.Cancelled) {
throw new WebException("The operation has been cancelled");
}
if (completedInfo.Error != null) {
throw completedInfo.Error;
}
return GetResponseData(completedInfo);
} finally {
AllDone(shouldCancel);
}
}
private byte[] GetResponseData(AsyncCompletedEventArgs e) {
return e is UploadDataCompletedEventArgs ?
((UploadDataCompletedEventArgs)e).Result :
((DownloadDataCompletedEventArgs)e).Result;
}
private void OnRequestCompleted(object sender, AsyncCompletedEventArgs e) {
lock (sync) {
if (requestDoneSignal != null) {
completedInfo = e;
requestDoneSignal.Set();
}
}
}
private void AllDone(bool shouldCancel) {
lock (sync) {
requestDoneSignal.Close();
requestDoneSignal = null;
client.UploadDataCompleted -= OnRequestCompleted;
client.DownloadDataCompleted -= OnRequestCompleted;
if (shouldCancel) {
client.CancelAsync();
}
}
}
}
}
【问题讨论】:
标签: c# multithreading http iis