.Net支持对过程或函数进行异步调用,本文通过引入SemaphoreSlim信号量,实现对并行执行的任务的并行数量控制。
并行执行并等待多个任务时,可能需要注意:
- 变量作用域与意外更新:尽管您可以直接通过Lambda子句,将主线程作用域的变量传递给子线程。但是,建议不要传递作用域大于创建子线程的语句所在的作用域的变量。否则,您可能会遇到变量被意外修改的问题。
- 例如,若您在For循环中设置定义在循环之外的变量GlobalVar,并通过Task.Factory.StartNew()语句建立子线程,该线程执行体中同样引用了GlobalVar变量。您可能会预期每个子线程使用其被建立时的GlobalVar的副本,但事实上每个子线程会在执行时直接引用主线程中GlobalVar的当前值。
- 为此,您应当在通过Task.Factory.StartNew()语句建立子线程前,手动建立GlobalVar的局部副本GlobalVarLocalBackup,并在子线程中引用GlobalVarLocalBackup。
- 若您的应用程序具有用户界面,您不应在子线程中直接引用用户界面上的控件。
下例中,函数DoWorkAsync()位于主线程中,并会在执行时异步进行一系列操作。子线程会引用主线程中的全局变量GlobalVar1、局部变量LocalVar1和用户界面控件txtScale的值,并返回结果字典。通过全局变量ParallelCount限制并行执行的任务数量(本例中使用物理处理器个数的一半)。
Imports System.Threading.Tasks
'Global variables
Dim GlobalVar1 As String
Private Async Sub DoWorkAsync()
'Determine parallel count
Dim ParallelCount As Integer = Int(Environment.ProcessorCount / 2)
If ParallelCount < 1 Then
ParallelCount = 1
End If
'Create semaphore
Dim ParallelSemaphore As New SemaphoreSlim(ParallelCount)
'Create task list
Dim TaskList As New List(Of Task(Of Dictionary(Of String, String)))
'Create child threads
For i As Integer = 1 To 245
'Update global variable and create local snapshot
GlobalVar1 = i.ToString() & "^2 = "
Dim GlobalVar1LocalBackup As String = GlobalVar1
'Create local variable
Dim LocalVar1 As Integer = i
Dim CurrentScale As Double = Val(txtScale.Text)
'Run child thread
Dim CurrentTask As Task(Of Dictionary(Of String, String)) = _
Task.Factory.StartNew(Function()
'Result dictionary
Dim ResultDict As New Dictionary(Of String, String)
'Wait for executing slot
ParallelSemaphore.Wait()
'Do work
Dim NumResult As Double = (LocalVar1 * CurrentScale) * (LocalVar1 * CurrentScale)
Dim StrResult As String = GlobalVar1LocalBackup & NumResult.ToString()
ResultDict.Add("Input", LocalVar1.ToString())
ResultDict.Add("Output", NumResult.ToString())
ResultDict.Add("Message", StrResult)
'Release executing slot for next worker
ParallelSemaphore.Release()
Return ResultDict
End Function)
TaskList.Add(CurrentTask)
Next
'Wait for results
For Each CurrentTask In TaskList
'Wait asynchronously
Await Task.Factory.StartNew(Sub()
CurrentTask.Wait()
End Sub)
'Get result
Dim ResultDict As New Dictionary(Of String, String) = CurrentTask.Result
Next
End Sub若您正在使用.Net Framework 4.0,那么您需要在项目中引用Microsoft.Bcl和Microsoft.Bcl.Async等2个程序集,方能正常使用Await关键字。否则会提示“找不到“async”修饰符所需的所有类型。是否面向了错误的框架版本,或缺少对程序集的引用?”错误。您还需要在App.config文件的@configuration@节中的@runtime@节(您可能需要手动加入@runtime></runtime@节)内添加assemblyBinding重定向:
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.8.0" newVersion="2.6.8.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.8.0" newVersion="2.6.8.0" />
</dependentAssembly>
</assemblyBinding>参考资料:
https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.taskfactory.startnew
https://stackoverflow.com/questions/2898609
https://stackoverflow.com/questions/8127316
https://www.cnblogs.com/qinjin/p/Task-Run-Vs-Task-Factory-StartNew.html





