我正在开发一个应用程序,在某个时候我正在对对象的运动进行一些CPU密集型计算。在某些时候,我正在使用Task.Run()来调用执行工作的函数,这会导致一些意外的结果,我不确定这是否会被视为竞争条件或是否具有其他名称。我的实际代码相当庞大,因此我将问题简化为可以在.net Framework控制台应用程序中运行的最小示例。
对于MWE,请考虑以下类,它具有3个字段和一个初始化它们的构造函数。它还具有一个report()
子项,以便于调试。
Public Class DebugClass
Public Variable1 As Double
Public Variable2 As Double
Public Variable3 As Double
Public Sub New(Variable1 As Double,Variable2 As Double,Variable3 As Double)
Me.Variable1 = Variable1
Me.Variable2 = Variable2
Me.Variable3 = Variable3
End Sub
Public Sub Report()
Console.WriteLine()
Console.WriteLine("Variable1: {0},Variable2: {1},Variable3: {2}",Variable1,Variable2,Variable3)
End Sub
End Class
我还有另一个帮助程序功能,它以0到1秒的随机延迟代替了我的实际应用程序需要的CPU密集型工作:
Public Async Function RandomDelayAsync() As Task
Await Task.Delay(TimeSpan.FromSeconds(Rnd()))
End Function
出于演示目的,我有2个版本的“工作”功能; Async
的非异步版本。这些函数中的每一个均以DebugClass
的实例作为参数,假装对其进行一些工作,然后简单地返回它作为输入得到的同一对象。 :
'Async version
Public Async Function DoSomeWorkAsync(WorkObject As DebugClass) As Task(Of DebugClass)
Await RandomDelayAsync()
Return WorkObject
End Function
'Synchronous version
Public Function DoSomeWork(WorkObject As DebugClass) As DebugClass
RandomDelayAsync.Wait()
Return WorkObject
End Function
最后,我有一个WaiterLoop
,此函数Awaits
创建了一个要完成的任务,将返回的对象的字段打印到控制台并将其从任务列表中删除。然后,它等待下一个完成。在我的实际应用程序中,从各个任务获得结果后,我将在此处进行更多计算,以查看哪些参数给出了最佳结果。
Public Async Function WaiterLoop(Tasklist As List(Of Task(Of DebugClass))) As Task
Dim Completed As Task(Of DebugClass)
Do Until Tasklist.Count = 0
Completed = Await Task.WhenAny(Tasklist)
Completed.Result.Report()
Tasklist.Remove(Completed)
Loop
End Function
现在,首先考虑我的Main()
函数的这个版本:
Sub Main()
Randomize()
Dim Tasklist As New List(Of Task(Of DebugClass))
Dim anInstance As DebugClass
For var1 As Double = 0 To 5 Step 0.5
For var2 As Double = 1 To 10 Step 1
For Var3 As Double = -5 To 0 Step 1
anInstance = New DebugClass(var1,var2,Var3)
'adding an Async task to the tasklist
Tasklist.Add(DoSomeWorkAsync(anInstance))
Next
Next
Next
WaiterLoop(Tasklist).Wait()
Console.ReadLine()
End Sub
这里的输出完全符合我的期望,所有任务均已完成,并且对于每个参数组合,将一行打印到控制台。到目前为止一切都很好,当此行出现时,我所面临的问题再次出现:
Tasklist.Add(DoSomeWorkAsync(anInstance))
已替换为此行
Tasklist.Add(Task.Run(Function() DoSomeWork(anInstance)))
在这个新版本中,我不调用工作功能的Async
版本,而是使用Task.Run
在工作线程上运行正常的同步功能。这是发狂的地方。
突然,输出不再符合预期;
'This is the type of output i now get:
Variable1: 1.5,Variable2: 7,Variable3: -1
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable3: 0
我现在创建的所有任务似乎都在引用DebugClass
的相同实例,因为每次任务完成时,都会输出相同的输出。我不明白为什么会这样,因为我每次启动新任务之前先创建一个DebugClass
的新实例:anInstance = New DebugClass(var1,Var3)
,然后是Tasklist.Add(Task.Run(Function() DoSomeWork(anInstance)))
。一旦我将DebugClass
的新实例分配给AnInstance
,它就会“忘记”它存储的先前实例,对吗?每个创建的任务所引用的实例都应该独立于其他任务所引用的实例吗?
很显然,我误会了,但是如果有人可以向我解释这里发生了什么,我将不胜感激。
我也想知道为什么一个比另一个要快,但是除非与这个问题有关,否则我将把它保存在一个单独的问题中。
感谢您抽出宝贵的时间阅读本文章。