.NET 任务和字典 - 写得太快

.NET Tasks and Dictionary - Writing too fast

我正在使用 .NET Tasks 来获得多线程的优势,但实现起来并不容易。

为了演示我遇到的问题,我编写了实际代码的简化版本。

执行代码后发现,最终的AllResults字典确实有(Nothing, Nothing)的KeyValuepairs——这是不可能添加的。

经过一番思考,我想我找到了为什么会发生,但我找不到解决办法。

我猜这是因为子任务将键值对添加到主级字典 "AllResults" 太快了 - 当字典达到它的大小时不允许它分配更多 space。 据我所知,当字典变满时,它会将自身调整为当前大小的两倍。但我猜由于使用了任务(运行 在其他线程上),它在调整自身大小时会增加一倍以上,这会导致 Nothing (null) 元素。

.NET 必须尝试通过将元素添加为 (nothing,nothing) 来防止内存访问错误(可能是蓝屏)。实际上,这是一个很好的行为,证明了字典在 .NET 中是如何写得很好的。它可以跳过添加,但我们可能不知道会导致数据丢失的错误。

但是,如何解决这个问题呢?

现在谢谢了。

Imports System.Threading.Tasks

Module Module1

    Public AllResults As Dictionary(Of Integer, SomeClass) 'this is the issue

    Public Class SomeClass

        Public Property ID As Integer
        Public Property Name As String

        Public ADictionary As Dictionary(Of Integer, Integer) = New Dictionary(Of Integer, Integer)

        Public subClasses As Dictionary(Of Integer, SomeClass)

        Public Sub New()

        End Sub

    End Class

    Sub Main()

        AllResults = New Dictionary(Of Integer, SomeClass)

        Dim Classes As Dictionary(Of Integer, SomeClass) = New Dictionary(Of Integer, SomeClass)
        Classes.Add(10, New SomeClass With {.ID = 1, .Name = "10"})
        Classes.Add(20, New SomeClass With {.ID = 2, .Name = "20"})
        Classes.Add(40, New SomeClass With {.ID = 3, .Name = "40"})
        Classes.Add(80, New SomeClass With {.ID = 4, .Name = "80"})
        Classes.Add(160, New SomeClass With {.ID = 5, .Name = "160"})
        Classes.Add(320, New SomeClass With {.ID = 6, .Name = "320"})
        Classes.Add(640, New SomeClass With {.ID = 7, .Name = "640"})
        Classes.Add(1280, New SomeClass With {.ID = 8, .Name = "1280"})

        Dim MainTasks(Classes.Count - 1) As Task

        Dim MTX As Integer = 0

        Dim Depth As Integer = 3

        For Each sc As SomeClass In Classes.Values
            MainTasks(MTX) = Task.Factory.StartNew(Sub()
                                                       DoCalculation(sc, Depth)
                                                   End Sub)
            MTX += 1
        Next

        Task.WaitAll(MainTasks)

        Console.WriteLine("Completed.")

        Dim IE As IOrderedEnumerable(Of KeyValuePair(Of Integer, SomeClass))


        IE = AllResults.OrderBy(Function(v) v.Value.ID)


        For Each vkvp As KeyValuePair(Of Integer, SomeClass) In IE
        Next


        Console.ReadLine()

    End Sub


    Public Function DoCalculation(inputClass As SomeClass, depth As Integer) As Dictionary(Of Integer, SomeClass)

        'do some work here

        Dim newID As Integer, newClass As SomeClass

        inputClass.subClasses = New Dictionary(Of Integer, SomeClass)

        For x As Integer = 10 To 20
            newID = Integer.Parse(inputClass.ID.ToString + x.ToString)
            newClass = CloneClass(inputClass)
            newClass.ID = newID
            inputClass.subClasses.Add(x, newClass)
        Next

        If depth > 0 Then

            Dim SubTasks(inputClass.subClasses.Count - 1) As Task
            Dim STX As Integer = 0

            For Each c As SomeClass In inputClass.subClasses.Values
                AllResults.Add(c.ID, c)
                SubTasks(STX) = Task.Factory.StartNew(Sub()
                                                          DoCalculation(c, depth - 1)
                                                      End Sub, TaskCreationOptions.AttachedToParent)
                STX += 1
            Next

            Task.WaitAll(SubTasks)

        End If

        Return inputClass.subClasses

    End Function

    Public Function CloneClass(inputClass As SomeClass) As SomeClass

        Dim newClass As SomeClass = New SomeClass

        newClass.Name = "Clone of " + inputClass.Name

        newClass.ADictionary = New Dictionary(Of Integer, Integer)(inputClass.ADictionary)

        Return newClass

    End Function

End Module

Dictionary 不是线程安全的集合(默认情况下,所有 .NET 对象都不是,除非文档另有说明)。

这意味着当从多个线程同时使用时,它没有被编程为正常工作。

您必须:

  • 在所有 AllResults 用法中使用锁等同步原语。
  • 使用线程安全的集合,看看System.Collections.Concurrent