引用Winform传递Hashtable;在 BGW 中使用此变量在取消 BGW 时不更新最初传递的哈希表

Pass Hashtable by reference to Winform; usage of this variable in BGW not updating originally passed hashtable when canceling BGW

我想将哈希表传递给 winform 并使用后台工作程序向哈希表添加一些值。如果我取消 BGW 操作,哈希表应该恢复到它的原始值(即在 BGW 启动之前)。

使用 BGW 中的以下代码 SomeHashtable returns,在取消之前添加了值,即使我在取消时尝试将其重置为 RunWorkerCompleted 中的原始状态。我需要帮助来理解这里的问题以及如何正确实施重置。

我在下面准备了一个简短的示例,其中包含一个带有两个按钮(“添加”和“取消”)的表单。

Module Module1

Sub Main()
    Dim SomeHashtable As New Hashtable()
    SomeHashtable.Add(-1, -1)

    Dim dlgAdd As New frmFileAdd(SomeHashtable)
    dlgAdd.ShowDialog()

    System.Diagnostics.Debug.Print(SomeHashtable.Count) 'if user cancels BGW this should be 1 (-1,-1)
End Sub
End Module

Public Class frmFileAdd
    Private SomeHashtableForm As Hashtable = Nothing
    Private SomeHashtableInit As Hashtable = Nothing


    Public Sub New(ByRef _SomeHashtable As Hashtable)

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        SomeHashtableForm = _SomeHashtable 

        SomeHashtableInit = SomeHashtableForm.Clone() 'shallow copy to reset SomeHashtable to its initial values if i cancel the BGW


    End Sub

    Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
        bgwAdd.RunWorkerAsync()
    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        If bgwAdd.IsBusy() And Not bgwAdd.CancellationPending Then
            bgwAdd.CancelAsync()
        Else
            Me.Close()
        End If

    End Sub

    Private Sub bgwAdd_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgwAdd.DoWork

        For iRun = 0 To 9
            If Not bgwAdd.CancellationPending Then
                SomeHashtableForm.Add(iRun, iRun)
                Thread.Sleep(1000)

            Else
                e.Cancel = True
                Exit For
            End If

        Next
    End Sub

    Private Sub bgwAddFiles_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwAdd.RunWorkerCompleted
        If e.Cancelled Then
            SomeHashtableForm = SomeHashtableInit 'I was expecting that this will also affect the ByRef _SomeHashtable variable used in the constructor but it is not
            'SomeHashtableForm = New Hashtable(SomeHashtableInit) i also tried this
            Me.Close()

        Else
            'Do something else
        End If
    End Sub


End Class

正如我在评论中提到的,很明显您并不真正了解值类型和引用类型或按值和按引用传递的实际工作原理。这里:

Public Sub New(ByRef _SomeHashtable As Hashtable)

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

    SomeHashtableForm = _SomeHashtable 

    SomeHashtableInit = SomeHashtableForm.Clone() 'shallow copy to reset SomeHashtable to its initial values if i cancel the BGW


End Sub

那个ByRef根本没有用。通过引用传递引用类型 (class) 的唯一方法是在该方法内将不同的对象分配给该参数。您没有为该构造函数中的参数分配任何内容,因此声明它 ByRef 毫无意义。此外,您绝对不应该声明构造函数参数 ByRef。构造函数的目的是构造和对象,而不是传递数据。

这里:

If e.Cancelled Then
    SomeHashtableForm = SomeHashtableInit

您所做的只是将表单的一个字段分配给另一个字段。这对表格以外的任何东西的影响完全为零。这两个字段都是私有的,不会以任何方式公开,因此无法从表单外部访问它们。

你究竟应该做什么还有待商榷,因为你能做的绝对不止一件事。我建议你应该做的是在 RunWorkerCompleted 事件之前根本不要触摸原始词典。只有在那个时候你才应该填充原始字典,当且仅当工作没有被取消时。这可能看起来像这样:

Module Module1

    Sub Main()
        Dim data As New Dictionary(Of Integer, Integer) From {{-1, -1}}

        Using dialogue As New Form1(data)
            If dialogue.ShowDialog() = DialogResult.OK Then
                Console.WriteLine($"Operation completed. Item count = {data.Count}")
            Else
                Console.WriteLine($"Operation cancelled. Item count = {data.Count}")
            End If
        End Using
    End Sub

End Module
Imports System.ComponentModel
Imports System.Threading

Public Class Form1

    Private data As Dictionary(Of Integer, Integer)

    Public Sub New(data As Dictionary(Of Integer, Integer))
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        Me.data = data
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        BackgroundWorker1.CancelAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim tempData As New Dictionary(Of Integer, Integer)

        For i = 0 To 9
            If BackgroundWorker1.CancellationPending Then
                e.Cancel = True
                Return
            End If

            tempData.Add(i, i)
            Thread.Sleep(1000)
        Next

        e.Result = tempData
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        If e.Cancelled Then
            DialogResult = DialogResult.Cancel
        Else
            Dim tempData = DirectCast(e.Result, Dictionary(Of Integer, Integer))

            For Each kvp In tempData
                data.Add(kvp.Key, kvp.Value)
            Next

            DialogResult = DialogResult.OK
        End If
    End Sub

End Class

在这种情况下,数据被添加到一个完全独立的 Dictionary 中,并且仅在工作完成时才转移到原始文件中,而不会被取消。请注意,在实际情况下,这可能意味着您最终会得到重复的密钥。为避免这种情况,您应该在添加到临时 Dictionary 之前检查原始 Dictionary

If data.ContainsKey(i) Then
    'This key can't be added to the original data so decide what to do about it.
Else
    tempData.Add(i, i)
End If