You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VB.NET异步大文件复制改造:实现暂停、停止与进度统计

改造VB.NET批量文件复制为异步模式(支持暂停/停止与进度追踪)

我来帮你把这段同步的批量文件复制代码改造成支持异步执行、暂停/停止功能和实时进度追踪的版本。核心思路是利用VB.NET的Async/Await异步模式、取消令牌机制和安全的UI进度更新方案,彻底解决原代码阻塞UI、无法中断的问题。


完整修改后的代码

首先我们需要添加类级别的控制字段,然后重构核心逻辑为异步模式:

' 异步操作控制字段
Private _cts As CancellationTokenSource
Private _pauseEvent As ManualResetEventSlim
Private _progress As IProgress(Of ProgressUpdate)

' 自定义进度更新实体,用于传递进度数据
Private Class ProgressUpdate
    Public Property ProcessedRecords As Integer
    Public Property CopiedImages As Integer
    Public Property TotalRecords As Integer
End Class

' 异步版本的主操作按钮事件
Private Async Sub btnMoverImagenes_Click(sender As Object, e As EventArgs) Handles btnMoverImagenes.Click
    ' 初始化异步控制对象
    _cts = New CancellationTokenSource()
    _pauseEvent = New ManualResetEventSlim(True) ' 初始状态为运行中
    _progress = New Progress(Of ProgressUpdate)(AddressOf UpdateProgressUI)

    Dim contadorIMGCopiadas As Integer = 0
    Dim contadorRegistros As Integer = 0
    Dim contadorArchivosSinImagen As Integer = 0
    Dim h1 As Date = System.DateTime.Now
    Dim fechaDesde As String
    Dim fechaHasta As String

    ' 日期格式化逻辑保持不变
    dtpDesde.Format = DateTimePickerFormat.Custom
    dtpDesde.CustomFormat = "yyyyMMdd"
    fechaDesde = dtpDesde.Text
    dtpHasta.Format = DateTimePickerFormat.Custom
    dtpHasta.CustomFormat = "yyyyMMdd"
    fechaHasta = dtpHasta.Text

    Try
        ' 获取数据(如果EjecutarXML支持异步,建议改为Async版本)
        Dim dsImagenes = EjecutarXML(conexionDat, fechaDesde, fechaHasta)
        If dsImagenes.Tables(dsImagenes.Tables.Count - 1).Rows(0).Item(3) <= 0 Then
            MsgBox("sin datos para la fecha seleccionada")
            Return
        End If

        Dim dsregistros = dsImagenes.Tables(0).Rows.Count
        ' 初始化进度条
        ProgressBar1.Minimum = 0
        ProgressBar1.Maximum = dsregistros
        ProgressBar1.Value = 0
        ProgressBar2.Minimum = 0
        ProgressBar2.Maximum = dsregistros
        ProgressBar2.Value = 0
        Label5.Text = $"0 de {dsregistros}"
        Label6.Text = $"0 de {dsregistros}"

        Log("******* SE INICIA CARGA *******")

        If dsregistros > 0 Then
            For Each row As DataRow In dsImagenes.Tables(0).Rows
                ' 检查是否被用户取消
                If _cts.Token.IsCancellationRequested Then
                    Log("******* PROCESO CANCELADO POR EL USUARIO *******")
                    Exit For
                End If

                ' 检查是否处于暂停状态,暂停则等待直到继续或取消
                _pauseEvent.Wait(_cts.Token)

                ' 提取行数据(和原逻辑一致)
                Dim archivo = row.Item(0).ToString()
                Dim carpetaProducto = row.Item(3).ToString()
                Dim rutaOrigen = row.Item(1).ToString()
                Dim idregistro = Convert.ToInt32(row.Item(2))
                Dim agno = row.Item(4).ToString()
                Dim mes = row.Item(5).ToString()
                Dim dia = row.Item(6).ToString()
                Dim secuencia = row.Item(7).ToString()
                Dim fechaCarga = row.Item(9).ToString()
                Dim imagenMulti = row.Item(8).ToString()
                Dim nombreImagenOrigen = row.Item(10).ToString()

                Dim nuevaRutaImagenMulti = $"{carpetaProducto}\{agno}\{mes}\{dia}\{secuencia}\{nombreImagenOrigen}"
                Dim RutaDestinoOp = $"{RutaDestinoNasOp}\{carpetaProducto}\{agno}\{mes}\{dia}\{secuencia}"
                Dim RutaDestinoHis = $"{RutaDestinoNasHis}\{carpetaProducto}\{agno}\{mes}\{dia}\{secuencia}"
                Dim DestinoArchivoOrigen = $"{RutaDestinoNasOp}\{carpetaProducto}\{agno}\{mes}\{dia}\{secuencia}\{nombreImagenOrigen}"
                Dim DestinoArchivoHistorico = $"{RutaDestinoNasHis}\{carpetaProducto}\{agno}\{mes}\{dia}\{secuencia}\{archivo}"

                Log("------------------------------------------------------------------------------")
                Log($"******* Inicio copia archivo IDREGISTRO: {idregistro} *******")

                Try
                    ' 核心业务逻辑,替换为异步方法调用
                    If ValidaExisteImagenOrigen(rutaOrigen) Then
                        If ValidarRutaOrigen(rutaOrigen) Then
                            If Await CargaNasHistoricoAsync(rutaOrigen, RutaDestinoHis, DestinoArchivoHistorico, conexionDat, idregistro, _cts.Token) Then
                                If Await CargaNasOperativoAsync(rutaOrigen, RutaDestinoOp, DestinoArchivoOrigen, conexionDat, idregistro, nuevaRutaImagenMulti, _cts.Token) Then
                                    contadorIMGCopiadas += 1
                                End If
                            End If
                        Else
                            If ValidaImagenHistorico(DestinoArchivoHistorico) Then
                                If Await CargaNasHistoricoAsync(rutaOrigen, RutaDestinoHis, DestinoArchivoHistorico, conexionDat, idregistro, _cts.Token) Then
                                    contadorIMGCopiadas += 1
                                Else
                                    Log($"******* TODO OK, Registro id: {idregistro}. no se realiza copia de imagen *******")
                                    contadorIMGCopiadas += 1
                                End If
                            End If
                        End If
                    Else
                        Log($"******* ALERTA: Registro id: {idregistro} NO EXISTE *******")
                        contadorArchivosSinImagen += 1
                    End If
                Catch ex As OperationCanceledException
                    Log($"******* Copia de archivo {idregistro} cancelada *******")
                    Exit For
                Catch ex As Exception
                    Log($"******* Error al procesar archivo {idregistro}: {ex.Message} *******")
                End Try

                contadorRegistros += 1
                Log($"******* Finaliza copia archivo IDREGISTRO: {idregistro} *******")

                ' 报告最新进度
                _progress.Report(New ProgressUpdate With {
                    .ProcessedRecords = contadorRegistros,
                    .CopiedImages = contadorIMGCopiadas,
                    .TotalRecords = dsregistros
                })
            Next

            ' 完成统计
            Dim h2 As Date = System.DateTime.Now
            Dim tiempoCarga = (h2 - h1).ToString()
            MsgBox("Proceso Terminado, revisar log para ver el detalle del proceso completo")
            Log("------------------------------------------------------------------------------")
            Log($"******* Copia Finalizada, IMAGENES COPIADAS: {contadorIMGCopiadas} DE {dsregistros} *******")
            Log($"******* REGISTROS SIN IMAGENES: {contadorArchivosSinImagen} *******")
            Log($"******* TIEMPO DE EJECUCION: {tiempoCarga} *******")
        End If
    Catch ex As Exception
        MsgBox($"ocurrio un error al buscar los datos: {ex.Message}")
        Log($"******* ERROR GENERAL: {ex.Message} *******")
    Finally
        ' 清理资源,避免内存泄漏
        _cts?.Dispose()
        _pauseEvent?.Dispose()
    End Try
End Sub

' 安全更新UI进度的方法(自动在UI线程执行)
Private Sub UpdateProgressUI(update As ProgressUpdate)
    ProgressBar1.Value = update.ProcessedRecords
    ProgressBar2.Value = update.CopiedImages
    Label5.Text = $"{update.ProcessedRecords} de {update.TotalRecords}"
    Label6.Text = $"{update.CopiedImages} de {update.TotalRecords}"
End Sub

' 异步版本的历史文件复制方法
Private Async Function CargaNasHistoricoAsync(rutaOrigen As String, rutaDestino As String, destinoArchivo As String, conexionDat As String, idregistro As Integer, token As CancellationToken) As Task(Of Boolean)
    Try
        ' 异步创建目录
        Await Task.Run(Sub()
                           If Not Directory.Exists(rutaDestino) Then
                               Directory.CreateDirectory(rutaDestino)
                           End If
                       End Sub, token)

        ' 异步复制文件
        Await File.CopyAsync(rutaOrigen, destinoArchivo, True, token)

        ' 替换为你的异步数据库操作(示例)
        ' Using conn As New SqlConnection(conexionDat)
        '     Await conn.OpenAsync(token)
        '     Using cmd As New SqlCommand("UPDATE ...", conn)
        '         cmd.Parameters.AddWithValue("@Id", idregistro)
        '         Await cmd.ExecuteNonQueryAsync(token)
        '     End Using
        ' End Using

        Return True
    Catch ex As OperationCanceledException
        Throw ' 向上抛出取消异常,让上层处理
    Catch ex As Exception
        Log($"******* Error en CargaNasHistoricoAsync para {idregistro}: {ex.Message} *******")
        Return False
    End Try
End Function

' 异步版本的操作文件复制方法
Private Async Function CargaNasOperativoAsync(rutaOrigen As String, rutaDestino As String, destinoArchivo As String, conexionDat As String, idregistro As Integer, nuevaRutaImagenMulti As String, token As CancellationToken) As Task(Of Boolean)
    Try
        Await Task.Run(Sub()
                           If Not Directory.Exists(rutaDestino) Then
                               Directory.CreateDirectory(rutaDestino)
                           End If
                       End Sub, token)

        Await File.CopyAsync(rutaOrigen, destinoArchivo, True, token)

        ' 这里添加原方法中的其他业务逻辑,全部改为异步实现
        Return True
    Catch ex As OperationCanceledException
        Throw
    Catch ex As Exception
        Log($"******* Error en CargaNasOperativoAsync para {idregistro}: {ex.Message} *******")
        Return False
    End Try
End Function

' 停止按钮事件
Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
    _cts?.Cancel()
    _pauseEvent?.Set() ' 唤醒暂停的线程以便取消
    MsgBox("Proceso cancelado")
End Sub

' 暂停/继续按钮事件
Private Sub btnPause_Click(sender As Object, e As EventArgs) Handles btnPause.Click
    If _pauseEvent Is Nothing Then Return
    If _pauseEvent.IsSet Then
        _pauseEvent.Reset() ' 进入暂停状态
        btnPause.Text = "继续"
    Else
        _pauseEvent.Set() ' 恢复运行
        btnPause.Text = "暂停"
    End If
End Sub

关键改造要点解释

  1. Async/Await异步模式

    • 将按钮事件改为Async Sub,确保UI线程不会被阻塞,用户可以随时点击暂停/停止按钮
    • 所有耗时操作(文件复制、数据库操作)都封装为异步方法,用Await等待执行完成
  2. 取消令牌(CancellationTokenSource)

    • 用于触发停止操作,所有异步方法都接受CancellationToken参数,实时检查是否被取消
    • 取消时会抛出OperationCanceledException,我们可以捕获并终止流程
  3. 暂停控制(ManualResetEventSlim)

    • 比普通布尔标志+Sleep更高效的暂停实现,暂停时调用Reset()让线程等待,继续时调用Set()唤醒线程
    • 等待过程中仍会响应取消操作,避免暂停后无法停止的问题
  4. 安全的UI进度更新

    • 使用IProgress<T>接口配合Progress<T>类,自动在UI线程执行进度更新回调,避免跨线程操作控件的异常
    • 自定义ProgressUpdate类传递多维度进度数据(已处理记录数、已复制文件数等)

注意事项

  • 如果EjecutarXML方法是耗时操作,建议也改为异步版本(EjecutarXMLAsync),避免初始化阶段阻塞UI
  • 数据库操作必须使用异步API(如OpenAsync、ExecuteNonQueryAsync),否则仍会阻塞线程
  • 日志写入操作如果是文件IO,建议改为File.AppendAllTextAsync异步方法,提升整体性能

内容的提问来源于stack exchange,提问作者Throkar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:19:07