在事件处理程序期间处置的WebClient子类,结果是ObjectDisposedException

时间:2012-08-23 16:13:21

标签: .net

我将WebClient子类化以获得对其功能的更多控制(更改Timeout,并提供每1MB进度触发的事件处理程序)。它连续(一次/分钟)从XML源轮询数据,并且大部分时间正常工作。但是,有一段时间,我得到一个我不理解的ObjectDisposedException。我将首先发布我认为相关部分的评论,然后发布整个源代码以供参考。

DownloadProgressChanged事件处理程序&处置

ObjectDisposedException发生在此处理程序的最后一行。变量处于注释中指示的状态。

void MyWebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    // cancelDueToError is false, isDisposed is true
    // isDisposed must have been false when this event handler fired
    if (cancelDueToError || isDisposed) return; 

    long additionalBytesReceived = e.BytesReceived - PreviousBytesReceived;
    PreviousBytesReceived = e.BytesReceived;
    BytesNotNotified += additionalBytesReceived;

    if (BytesNotNotified > ONE_MB)
    {
        OnNotifyMegabyteIncrement(e.BytesReceived);
        BytesNotNotified = 0;    // This is 0 at the point of the Exception, so this handler fired. 
    }
    firstByteReceived = true;

            //
            // !!!!! EXCEPTION HERE
            //
    // ObjectDisposedException here.  Clearly the class has been disposed already (isDisposed is true).  
    // But why is the DownloadProgressChanged event firing after MyWebClient is disposed?
    abortTimer.Change(TimeBetweenProgressChanges, System.Threading.Timeout.Infinite); 
}

// Somehow IDisposable.Dispose() is being called in the middle of the
// MyWebClient_DownloadProgressChanged event handler.  isDisposed is false
// at the beginning of the event handler (else it would have returned immediately)
// but true when I get the ObjectDisposedException
void IDisposable.Dispose()
{
    isDisposed = true;

    if (asyncWait != null) asyncWait.Dispose();
    if (abortTimer != null) abortTimer.Dispose();

    base.Dispose();
}

用法

using (MyWebClient webClient = new MyWebClient())
{
    webClient.NotifyMegabyteIncrement += new MyWebClient.PerMbHandler(webClient_NotifyMegabyteIncrement);
    bool downloadOK = webClient.DownloadFileWithEvents(url, outputPath);
    // Do stuff with downloaded file if downloadOK
}   

显然,MyWebClient有时会被放置在MyWebClient_DownloadProgressChanged事件处理程序的 middle 中。但是,在下载完成之前,不应丢弃该对象。怎么会发生这种情况?

完整源代码

public class MyWebClient : WebClient, IDisposable
{
    public int Timeout { get; set; }
    public int TimeUntilFirstByte { get; set; }
    public int TimeBetweenProgressChanges { get; set; }

    public long PreviousBytesReceived { get; private set; }
    public long BytesNotNotified { get; private set; }

    public string Error { get; private set; }
    public bool HasError { get { return Error != null; } }

    private bool firstByteReceived = false;
    private bool success = true;
    private bool cancelDueToError = false;

    private EventWaitHandle asyncWait = new ManualResetEvent(false);
    private Timer abortTimer = null;
    private bool isDisposed = false;

    const long ONE_MB = 1024 * 1024;

    public delegate void PerMbHandler(long totalMb);

    public event PerMbHandler NotifyMegabyteIncrement;

    public MyWebClient(int timeout = 60000, int timeUntilFirstByte = 30000, int timeBetweenProgressChanges = 15000)
    {
        this.Timeout = timeout;
        this.TimeUntilFirstByte = timeUntilFirstByte;
        this.TimeBetweenProgressChanges = timeBetweenProgressChanges;

        this.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(MyWebClient_DownloadFileCompleted);
        this.DownloadProgressChanged += new DownloadProgressChangedEventHandler(MyWebClient_DownloadProgressChanged);

        abortTimer = new Timer(AbortDownload, null, TimeUntilFirstByte, System.Threading.Timeout.Infinite);
    }

    protected void OnNotifyMegabyteIncrement(long totalMb)
    {
        if (NotifyMegabyteIncrement != null) NotifyMegabyteIncrement(totalMb);
    }

    void AbortDownload(object state)
    {
        cancelDueToError = true;
        this.CancelAsync();
        success = false;
        Error = firstByteReceived ? "Download aborted due to >" + TimeBetweenProgressChanges + "ms between progress change updates." : "No data was received in " + TimeUntilFirstByte + "ms";
        asyncWait.Set();
    }

    void MyWebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        if (cancelDueToError || isDisposed) return;

        long additionalBytesReceived = e.BytesReceived - PreviousBytesReceived;
        PreviousBytesReceived = e.BytesReceived;
        BytesNotNotified += additionalBytesReceived;

        if (BytesNotNotified > ONE_MB)
        {
            OnNotifyMegabyteIncrement(e.BytesReceived);
            BytesNotNotified = 0;
        }
        firstByteReceived = true;
        abortTimer.Change(TimeBetweenProgressChanges, System.Threading.Timeout.Infinite);
    }

    public bool DownloadFileWithEvents(string url, string outputPath)
    {
        asyncWait.Reset();
        Uri uri = new Uri(url);
        this.DownloadFileAsync(uri, outputPath);
        asyncWait.WaitOne();

        return success;
    }

    void MyWebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        if (cancelDueToError || isDisposed) return;
        asyncWait.Set();
    }

    protected override WebRequest GetWebRequest(Uri address)
    {            
        var result = base.GetWebRequest(address);
        result.Timeout = this.Timeout;
        return result;
    }

    void IDisposable.Dispose()
    {
        isDisposed = true;

        if (asyncWait != null) asyncWait.Dispose();
        if (abortTimer != null) abortTimer.Dispose();

        base.Dispose();
    }
}

0 个答案:

没有答案