使用WebClient下载文件触发TargetInvocationException异常求助
我最近写了一段VB.NET代码,用来从指定URL下载更新包,文件确实能成功保存到本地,但每次下载完成都会抛出System.Reflection.TargetInvocationException异常,而且DownloadFileCompleted方法完全没触发,实在搞不懂哪里出问题了!
先贴一下我原来的代码:
Private Sub getPatch() Using client As New WebClient Dim url As String = My.Settings.host & "/update.zip" If Directory.Exists(Replace(fName, "\update.zip", "")) Then Directory.Delete(Replace(fName, "\update.zip", ""), True) End If Directory.CreateDirectory(Replace(fName, "\update.zip", "")) AddHandler client.DownloadProgressChanged, AddressOf ProgressChanged AddHandler client.DownloadFileCompleted, AddressOf DownloadFileCompleted client.DownloadFileAsync(New Uri(url), fName) End Using End Sub Public Sub ProgressChanged(ByVal sender As Object, ByVal e As System.Net.DownloadProgressChangedEventArgs) Try If e.ProgressPercentage = 100 Then Exit Sub ProgressBar1.Value = e.ProgressPercentage Catch ex As Exception MsgBox(ex.ToString) End Try End Sub Public Sub DownloadFileCompleted(ByVal sender As Object, ByVal e As System.Net.DownloadDataCompletedEventArgs) If Not e.Cancelled AndAlso e.Error Is Nothing Then MessageBox.Show("Download success") extractZip(fName) Else MessageBox.Show("Download failed") End If End Sub
抛出的异常信息是:
An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
Additional information: Exception has been thrown by the target of an invocation.
问题根源分析
我仔细排查后发现两个关键错误:
事件处理方法参数类型不匹配
WebClient.DownloadFileCompleted事件对应的参数类型是System.ComponentModel.AsyncCompletedEventArgs,但我写的DownloadFileCompleted方法用了System.Net.DownloadDataCompletedEventArgs——这个类型是给DownloadDataAsync事件用的!参数类型不匹配导致.NET反射调用事件处理方法时失败,直接抛出了TargetInvocationException,这也是为什么方法根本没触发的原因。WebClient被提前释放
原来的代码把WebClient放在Using块里,getPatch方法执行完毕后,Using块会立即Dispose掉WebClient,但异步下载操作此时还在后台运行。Client被提前释放后,后续的事件通知完全无法正常执行,进一步加剧了异常问题。
修正后的代码
针对这两个问题,我调整了代码结构,修复后的版本如下:
' 将WebClient声明为类级变量,避免提前被释放 Private client As WebClient Private Sub getPatch() client = New WebClient() Dim url As String = My.Settings.host & "/update.zip" Dim targetDir As String = Replace(fName, "\update.zip", "") ' 清理并创建目标目录(提取变量让代码更清晰) If Directory.Exists(targetDir) Then Directory.Delete(targetDir, True) End If Directory.CreateDirectory(targetDir) ' 绑定事件处理方法 AddHandler client.DownloadProgressChanged, AddressOf ProgressChanged AddHandler client.DownloadFileCompleted, AddressOf DownloadFileCompleted ' 启动异步下载 client.DownloadFileAsync(New Uri(url), fName) End Sub Public Sub ProgressChanged(ByVal sender As Object, ByVal e As System.Net.DownloadProgressChangedEventArgs) Try If e.ProgressPercentage = 100 Then Exit Sub ProgressBar1.Value = e.ProgressPercentage Catch ex As Exception MsgBox(ex.ToString) End Try End Sub Public Sub DownloadFileCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) ' 下载完成后手动释放WebClient,避免内存泄漏 Try If Not e.Cancelled AndAlso e.Error Is Nothing Then MessageBox.Show("Download success") extractZip(fName) Else ' 更友好的错误提示,区分取消和错误情况 Dim errorMsg As String = If(e.Error IsNot Nothing, $"Error: {e.Error.Message}", "Download cancelled by user") MessageBox.Show($"Download failed: {errorMsg}") End If Finally If client IsNot Nothing Then ' 先移除事件处理,再释放资源 RemoveHandler client.DownloadProgressChanged, AddressOf ProgressChanged RemoveHandler client.DownloadFileCompleted, AddressOf DownloadFileCompleted client.Dispose() client = Nothing End If End Try End Sub
修正点说明
- 把WebClient提升为类级变量,避免Using块提前释放它,确保异步下载全程client都处于可用状态。
- 修正
DownloadFileCompleted的参数类型为System.ComponentModel.AsyncCompletedEventArgs,和事件委托类型完全匹配,保证事件能正常触发。 - 在
DownloadFileCompleted的Finally块里手动移除事件处理并释放WebClient,避免内存泄漏,同时确保资源被正确清理。
内容的提问来源于stack exchange,提问作者XMozart

