Я работаю над приложением, где в какой-то момент я делаю некоторые интенсивные вычисления процессора для кинематики объекта. В какой-то момент я использую 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 секунды:
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()
function:
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
для запуска обычно синхронной функции в рабочем потоке. Это где s ** t попадает в вентилятор. Внезапно результат уже не такой, как ожидалось;
'This is the type of output i now get:
Variable1: 1.5,Variable2: 7,Variable3: -1
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Каким-то образом все созданные мной задачи теперь относятся к одному и тому же экземпляру DebugClass
, так как каждый раз, когда задача завершается, выводится один и тот же вывод. Я не понимаю, почему это происходит, потому что я создаю новый экземпляр DebugClass
перед каждым новым запуском новой задачи: anInstance = New DebugClass(var1, var2, Var3)
, а затем Tasklist.Add(Task.Run(Function() DoSomeWork(anInstance)))
. Как только я назначаю новый экземпляр DebugClass
на AnInstance
, он «забывает» предыдущий экземпляр, который хранил, верно? И экземпляр, на который ссылается каждая из созданных задач, должен быть независимым от тех, на которые ссылаются другие задачи?
Очевидно, я ошибаюсь, но я был бы признателен, если бы кто-то мог объяснить мне, что происходитздесь.
Мне также интересно, почему один работает быстрее, чем другой, но я оставлю это для отдельного вопроса, если он не связан с этой проблемой.
Спасибо, что нашли время, чтобы прочитать это.