.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
。
我正在使用 .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
。