超时和中止请求后无法运行第二个WebClient请求

时间:2016-04-22 17:28:58

标签: asynchronous webclient-download

我有一个桌面应用程序,它使用CustomWebClient对象并调用OpenReadAsync()同时下载一个或多个小文件(jpg,大小小于400KB,一次不超过20个)。如果流程中没有问题,下载过程就可以了。我想将响应限制在一定时间(15秒),所以我引入了一个timeOut处理,它正在中止请求。甚至超时都在工作,之后我的“OpenReadCompletedEventHandler”方法正在接收System.Net.WebException:请求被中止:请求被取消(这是正确的行为)。 现在,我的问题是我想让用户尝试重新加载图片。因此,下一个webClient请求失败并出现相同的WebException。以下是我的代码。

这是我的自定义WebClient类(用于一次拥有2个以上的异步连接):

internal class ExtendedWebClient : WebClient
{
    private Timer _timer;
    public int ConnectionLimit { get; set; }
    public int ConnectionTimeout { get; set; }
    public ExtendedWebClient()
    {
        this.ConnectionLimit = 2;
    }

    protected override WebRequest GetWebRequest(Uri address)
    {
        var request = base.GetWebRequest(address) as HttpWebRequest;

        if (request != null){_timer = new Timer(TimeoutRequest, request, ConnectionTimeout, Timeout.Infinite);

            request.ServicePoint.ConnectionLimit = this.ConnectionLimit;
            request.ServicePoint.MaxIdleTime = 5000;
            request.ServicePoint.ConnectionLeaseTimeout = 5000;
        }

        return request;
    }

    private void TimeoutRequest(object state)
    {
        _timer.Dispose();
        _timer = null;
        ((WebRequest)state).Abort();
    }

    protected override void Dispose(bool disposing)
    {
        if (_timer != null)
        {
            _timer.Dispose();
            _timer = null;
        }
        base.Dispose(disposing);
    }    
}

以下是使用我的自定义WebClient类下载文件的代码:

internal struct PageWaitHandleState
{
    public int WaitHandleIndexInPage;
    public bool ImageIsLoaded;
    public string ErrMessage;
}

public Image[] downloadedImages;

private PageWaitHandleState[] waitHandlesInPage;
private OpenReadCompletedEventHandler[] downloadComplete;
private EventWaitHandle[] pagesEWH = null;
private EventWaitHandle[] downloadImageEvent;
private int availableImages = 1;  // Set here to simplify, but as I stated in my description, it may be more than 1.
int downloadTimeOut = 15000;
int maxSimultaneousDownloads = 20;

private void DownloadImages(int pageIndex = 0, string[] imageUrl)
{
    if (pagesEWH[pageIndex] != null)
    {
        ReloadImages(pageIndex, imageUrl);  // Executed in the second request
        return;
    else
    {
        pagesEWH[pageIndex] = new EventWaitHandle[availableImages]; 
        downloadedImages = new Image[availableImages];
        downloadComplete = new OpenReadCompletedEventHandler[availableImages];
        downloadImageEvent = new EventWaitHandle[availableImages];
        waitHandlesInPage = new PageWaitHandleState[availableImages];

        // Set the downloadComplete deletages
        for (int i = 0; i < availableImages; i++)
        {
            downloadComplete[i] = ProcessImage;
        }
    }

    for (int imgCounter = 0; i < availableImages; i++)
    {
        waitHandlesInPage[imgCounter] = new PageWaitHandleState() { ImageIsLoaded = false, WaitHandleIndexInPage = imgCounter, ErrMessage = null };
        downloadImageEvent[imgCounter] = GrabImageAsync(imageUrl[imgCounter], downloadComplete[imgCounter], imgCounter, downloadTimeOut, maxSimultaneousDownloads);
        pagesEWH[imgCounter] = downloadImageEvent[imgCounter];
    }

        offenderIndex++;
    }
}

private static EventWaitHandle GrabImageAsync(string url, OpenReadCompletedEventHandler openReadCompletedEventHandler, int imgCounter, int downloadTimeOut, int maxSimultaneousDownloads)
{
    var myClient = new ExtendedWebClient();
    myClient.ConnectionLimit = maxSimultaneousDownloads;
    myClient.ConnectionTimeout = downloadTimeOut;
    myClient.OpenReadCompleted += openReadCompletedEventHandler;
    var iewh = new ImageEventWaitHandle() { ewh = new EventWaitHandle(false, EventResetMode.ManualReset), ImageIndex = imgCounter };
    myClient.OpenReadAsync(new Uri(url), iewh);
    return iewh.ewh;
}

internal void ProcessImage(object sender, OpenReadCompletedEventArgs e)
{
    ImageEventWaitHandle iewh = (ImageEventWaitHandle)e.UserState;
    bool disposeObject = false;

    try
    {
        if (e.Cancelled)
        {
            this.waitHandlesInPage[iewh.ImageIndex].ImageIsLoaded = false;
            this.waitHandlesInPage[iewh.ImageIndex].ErrMessage = "WebClient request was cancelled";
        }
        else if (e.Error != null)
        {
            this.waitHandlesInPage[iewh.ImageIndex].ImageIsLoaded = false;
            this.waitHandlesInPage[iewh.ImageIndex].ErrMessage = e.Error.Message;
            iewh.ewh.Set();
            this.downloadImageEvent[iewh.ImageIndex].Close();
        }
        else
        {
            using (Stream inputStream = e.Result)
            using (MemoryStream ms = new MemoryStream())
            {
                byte[] buffer = new byte[4096];
                int bytesRead;
                int totalReadBytes = 0;
                do
                {
                    bytesRead = inputStream.Read(buffer, 0, buffer.Length); // Exception fired here with the second request
                    ms.Write(buffer, 0, bytesRead);
                    totalReadBytes += bytesRead;
                } while (inputStream.CanRead && bytesRead > 0); 

                this.downloadedImages[iewh.ImageIndex] = Image.FromStream(ms);
                this.waitHandlesInPage[iewh.ImageIndex].ImageIsLoaded = true;
                this.waitHandlesInPage[iewh.ImageIndex].ErrMessage = null;
            }
            disposeObject = true;
        }
    }
    catch (Exception exc)
    {
        this.downloadedImages[iewh.ImageIndex] = null;
    }
    finally
    {
        // Signal the wait handle
        if (disposeObject)
        {
            iewh.ewh.Set();
            ((WebClient)sender).Dispose();
        }
    }
}

private void ReloadImages(int pageIndex, string[] imageUrl)
{
    for (int imgCounter = 0; imgCounter < availableImages; imgCounter++)
    {
        this.downloadComplete[imgCounter] = this.ProcessImage;
        this.waitHandlesInPage[imgCounter] = new PageWaitHandleState() { ImageIsLoaded = false, WaitHandleIndexInPage = imgCounter, ErrMessage = null };
        this.downloadImageEvent[imgCounter] = GrabImageAsync(ImageUrl[imgCounter],this.downloadComplete[imgCounter], imgCounter, downloadTimeOut, maxSimultaneousDownloads);
        this.pagesEWH[imgCounter] = this.downloadImageEvent[imgCounter];
    }
}

最后,当我想访问图像时,我会使用以下方法检查它们是否准备就绪:

private bool ImagesInPageReady(int pageIndex, int recordsInCurrentPage)
{
    if (_PagesEWH[pageIndex] != null)
    {
        int completedDownloadsCount = 0;
        bool waitHandleSet;

        // Wait for the default images first (imgCounter = 0). When moving page or asking for more pictures, then wait for the others.
        for (int ewhIndexInPage = 0; ewhIndexInPage < recordsInCurrentPage; ewhIndexInPage++)
        {
            if (this.pagesEWH[ewhIndexInPage].WaitOne(this.downloadTimeOut))
            {
                if (this.WaitHandlesInPage[ewhIndexInPage].ImageIsLoaded)
                {
                    completedDownloadsCount++;
                }
            }
            else
            {
                this.pagesEWH[ewhIndexInPage].Set();
            }
        }

        return (completedDownloadsCount > 0);
    }

    return false;
}

1 个答案:

答案 0 :(得分:0)

@usr,谢谢你指出我正确的方向。 HttpClient就是解决方案。所以我基本上将我的HttpClient对象封装在一个新类中,与ProcessImage()方法一起封装,并通过相同的方法触发公开和事件。