问题:我在ExtendedWebClient
中继承了WebClient
,在GetWebRequest
方法中重写了WebRequest
的超时属性。如果我把它设置为100毫秒,甚至20毫秒,它总是至少需要30秒以上。有时它似乎根本无法通过。
此外,当提供图像的服务(见下面的代码)再次联机时,Rx/System.Reactive中编写的代码不再将图像推入pictureBox?
我该怎么解决这个问题,我做错了什么?(参见下面的代码)
测试用例:我为此设置了一个WinForms测试项目,该项目正在执行以下操作。
-
GetNextImageAsync
public async Task<Image> GetNextImageAsync() { Image image = default(Image); try { using (var webClient = new ExtendedWebClient()) { var data = await webClient.DownloadDataTaskAsync(new Uri("http://SOMEIPADDRESS/x/y/GetJpegImage.cgi")); image = ByteArrayToImage(data); return image; } } catch { return image; } }
-
扩展的WebClient
private class ExtendedWebClient : WebClient { protected override WebRequest GetWebRequest(Uri address) { var webRequest = base.GetWebRequest(address); webRequest.Timeout = 100; return webRequest; } }
3.1演示代码(使用Rx)
注意:它实际上从未到达pictures.Subscribe()
正文中的"else"语句。
var pictures = Observable
.FromAsync<Image>(GetNextImageAsync)
.Throttle(TimeSpan.FromSeconds(.5))
.Repeat()
;
pictures.Subscribe(img => {
if (img != null) {
pictureBox1.Image = img;
} else {
if (pictureBox1.Created && this.Created) {
using (var g = pictureBox1.CreateGraphics()) {
g.DrawString("[-]", new Font("Verdana", 8), Brushes.Red, new PointF(8, 8));
}
}
}
});
3.2演示代码(使用Task.Run)
注意1:这里调用"else"主体,尽管WebClient的超时时间仍然比预期的要长。。。。
注意2:我不想使用这种方法,因为这样我就不能"节流"图像流,我无法按正确的顺序获取它们,也无法用我的图像流做其他事情。。。但这只是它工作的示例代码。。。
Task.Run(() => {
while (true) {
GetNextImageAsync().ContinueWith(img => {
if(img.Result != null) {
pictureBox1.Image = img.Result;
} else {
if (pictureBox1.Created && this.Created) {
using (var g = pictureBox1.CreateGraphics()) {
g.DrawString("[-]", new Font("Verdana", 8), Brushes.Red, new PointF(8, 8));
}
}
}
});
}
});
作为参考,将
byte[]
转换为Image
对象的代码。public Image ByteArrayToImage(byte[] byteArrayIn) { using(var memoryStream = new MemoryStream(byteArrayIn)){ Image returnImage = Image.FromStream(memoryStream); return returnImage; } }
其他问题
我将在下面解决取消问题,但对以下代码的行为也有误解,无论取消问题如何,这都会导致问题:
var pictures = Observable
.FromAsync<Image>(GetNextImageAsync)
.Throttle(TimeSpan.FromSeconds(.5))
.Repeat()
您可能认为这里的Throttle
会限制GetNextImageAsync
的调用率。遗憾的是,事实并非如此。考虑以下代码:
var xs = Observable.Return(1)
.Throttle(TimeSpan.FromSeconds(5));
您认为在订阅服务器上调用OnNext(1)
需要多长时间?如果你认为5秒钟,那你就错了。由于CCD_ 11在其CCD_ 13之后立即发送CCD_。
与此代码相比,我们创建了一个非终止流:
var xs = Observable.Never<int>().StartWith(1)
.Throttle(TimeSpan.FromSeconds(5));
现在OnNext(1)
在5秒后到达。
所有这一切的结果是,你的无限期Repeat
将攻击你的代码,在图像到达时尽快请求图像——这到底是如何造成你所看到的影响,还需要进一步分析。
根据您的要求,有几种结构可以限制查询速率。一种是简单地在结果中添加一个空延迟:
var xs = Observable.FromAsync(GetValueAsync)
.Concat(Observable.Never<int>()
.Timeout(TimeSpan.FromSeconds(5),
Observable.Empty<int>()))
.Repeat();
在这里,您可以用GetValueAsync
返回的类型替换int
。
取消
正如@StephenCleary所观察到的,设置WebRequest
的Timeout
属性将仅适用于同步请求。在研究了这些必要的更改以使用WebClient
干净地实现取消后,我不得不同意WebClient的问题,如果可能的话,最好转换为HttpClient
。
可悲的是,即便如此;"容易";诸如CCD_ 24之类的回拉数据的方法(出于某种奇怪的原因)不具有接受CCD_ 25的过载。
如果您确实使用HttpClient
,那么超时处理的一个选项是通过Rx,如下所示:
void Main()
{
Observable.FromAsync(GetNextImageAsync)
.Timeout(TimeSpan.FromSeconds(1), Observable.Empty<byte[]>())
.Subscribe(Console.WriteLine);
}
public async Task<byte[]> GetNextImageAsync(CancellationToken ct)
{
using(var wc = new HttpClient())
{
var response = await wc.GetAsync(new Uri("http://www.google.com"),
HttpCompletionOption.ResponseContentRead, ct);
return await response.Content.ReadAsByteArrayAsync();
}
}
在这里,我使用了Timeout
运算符来在超时的情况下发出一个空流——根据您的需要,还可以使用其他选项。
当Timeout
超时时,它将取消对FromAsync
的订阅,而CCD_29又将取消它通过GetNextImageAsync
间接传递给HttpClient.GetAsync
的取消令牌。
您也可以使用类似的构造在WebRequest
上调用Abort
,但正如我所说,由于没有直接支持取消令牌,这更麻烦。
引用MSDN文档:
Timeout属性仅影响使用GetResponse方法发出的同步请求。要使异步请求超时,请使用Abort方法。
可以乱用Abort
方法,但从WebClient
转换为HttpClient
更容易,因为CCD_36在设计时考虑到了异步操作。