我将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();
}
}