WebClient超时需要比预期更长的时间(使用:Rx,Try Catch,Task)

时间:2015-02-08 09:23:29

标签: c# timeout task webclient system.reactive

问题:我在WebClient中继承了ExtendedWebClient,我在WebRequest方法中覆盖了GetWebRequest的超时属性。如果我将其设置为100毫秒,甚至20毫秒,它至少需要30秒以上。有时它似乎根本无法通过。

此外,当服务图像的服务(请参阅下面的代码)再次重新联机时,用Rx / System.Reactive编写的代码不再将图像推送到pictureBox中了?

我怎样才能解决这个问题,我做错了什么? (见下面的代码)


测试用例:我为此设置了一个WinForms测试项目,该项目正在执行以下操作。

  1. 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;
        }
    }
    
  2. ExtendedWebClient

    private class ExtendedWebClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {                
            var webRequest = base.GetWebRequest(address);
            webRequest.Timeout = 100;
            return webRequest;
        }
    }
    
  3. 3.1演示代码(使用Rx)

    注意:它实际上从未到过&#34; else&#34; pictures.Subscribe()正文中的陈述。

        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 :这里&#34;其他&#34;正在调用body,尽管WebClient仍需要比预期更长的时间才能超时....

    注意事项2 :我不想使用这种方法,因为这样我就可以“节流”#34;图像流,我无法按照正确的顺序得到它们,并用我的图像流做其他事情......但这只是它工作的示例代码......

        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));
                            }
                        }
                    }
                });                    
            }
        });
    
    1. 作为参考,将byte[]转换为Image对象的代码。

      public Image ByteArrayToImage(byte[] byteArrayIn)
      {
          using(var memoryStream = new MemoryStream(byteArrayIn)){
              Image returnImage = Image.FromStream(memoryStream);
              return returnImage;
          }
      }
      

2 个答案:

答案 0 :(得分:3)

其他问题......

我将在下面解决取消问题,但是对以下代码的行为也存在误解,无论取消问题如何都会导致问题:

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秒钟,那你就错了。由于Observable.ReturnOnCompleted OnNext(1)推断之后立即发送Throttle,因此没有更多事件可能会限制OnNext(1)并立即发出它。

与我们创建非终止流的代码形成对比:

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();

在这里,您可以使用int返回的类型替换GetValueAsync

取消

正如@StephenCleary所观察到的,设置Timeout的{​​{1}}属性仅适用于同步请求。看了这个必要的变化以便用WebRequest干净利落地实现取消,我必须同意它与WebClient的关系,如果可能的话,你最好转换为WebClient

可悲的是,即便如此,&#34; easy&#34;用于撤回HttpClient不要等数据的方法(由于某些奇怪的原因)会超载接受GetByteArrayAsync

如果你使用CancellationToken,那么一个超时处理选项是通过Rx这样的:

HttpClient

这里我使用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会取消它通过HttpClient.GetAsync间接传递给GetNextImageAsync的取消令牌。

您也可以使用类似的结构在Abort上拨打WebRequest,但正如我所说的那样,它更多的是一个faff,因为那里没有直接的支持取消令牌。

答案 1 :(得分:3)

引用MSDN docs

  

Timeout属性仅影响使用GetResponse方法发出的同步请求。要使异步请求超时,请使用Abort方法。

可以使用Abort方法,但从WebClient转换为HttpClient更容易,它是使用异步设计的记住操作。