You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Async/Await为何出现多线程?如何限定仅在主线程运行?

问题描述

原本以为使用Async/Await的程序仅会占用1个线程:编译器遇到await关键字时,会在同一线程处理其他任务,直至等待的操作完成,仅等待的目标操作可能在其他线程执行,但程序本身不会创建新线程。但实际VB代码中出现主线程和工作线程卡在同一位置,疑惑是否是Task.WhenAll和Task.WhenAny导致了多线程,想知道如何让所有逻辑仅在主线程运行。


日志记录代码

Public Sub LogEvents(ByVal whatToLog As String, Optional ByVal canRandom As Boolean = True)
    Static logNumber As Integer
    Dim timeStamp As String

    timeStamp = CStr(Microsoft.VisualBasic.Now)

    whatToLog = timeStamp & "  " & whatToLog & Microsoft.VisualBasic.vbNewLine

    Try
        Debug.Print(whatToLog)
        System.IO.File.AppendAllText("log.txt", whatToLog, defaultEncoding)
    ' ... 省略后续代码
End Sub

任务创建代码

For Each account In uniqueAccounts().Values
    Dim newtask = account.getMarketInfoAsync().ContinueWith(Sub() account.LogFinishTask("Geting Market Info", starttime))
    LogEvents("marketinfo account " + account.Exchange + " is being done by task " + newtask.Id.ToString + " " + newtask.ToString)
    tasklist.Add(newtask)
    'newtask.ContinueWith(Sub() LogEvents(account.ToString))
Next

后续调用代码

LogEvents("Really Start Getting Market Detail of All")
Try
    Await jsonHelper.whenAllWithTimeout(tasklist.ToArray, 500000)
Catch ex As Exception
    Dim b = 1
End Try

whenAllWithTimeout函数代码

Public Shared Async Function whenAllWithTimeout(taskar As Task(), timeout As Integer) As Task
    Dim timeoutTask = Task.Delay(timeout)
    Dim maintask = Task.WhenAll(taskar)
    Await Task.WhenAny({timeoutTask, maintask})
    If maintask.IsCompleted Then
        Dim b = 1

        For Each tsk In taskar
            LogEvents("Not Time Out. Status of task " + tsk.Id.ToString + " is " + tsk.IsCompleted.ToString)
        Next
    End If

    If timeoutTask.IsCompleted Then
        Dim b = 1
        For Each tsk In taskar
            LogEvents("status of task " + tsk.Id.ToString + " is " + tsk.IsCompleted.ToString)
        Next
    End If
End Function

问题分析与解决方案

为什么会出现多线程?

  1. ContinueWith的默认调度逻辑:ContinueWith默认会把后续任务放到线程池线程执行,不会自动回到原上下文(比如主线程),这是工作线程出现的核心原因。Async/Await本身会在await恢复时回到原同步上下文,但ContinueWith不遵循这个规则。
  2. Task.WhenAll/WhenAny不创建线程:这两个方法只是等待任务完成,不会主动生成新线程,但如果传入的任务本身在多线程执行(比如getMarketInfoAsync内部用了线程池,或者ContinueWith导致的),自然会出现多线程运行的情况。
  3. LogEvents的竞态风险:LogEvents里的Static logNumber是线程不安全的,加上多线程同时调用文件写入操作,可能导致线程阻塞或冲突。

如何让所有逻辑在主线程运行?

  1. 用Await替换ContinueWith:这是最简洁可靠的方式,Async/Await会自动处理上下文切换,确保后续代码回到主线程:

    ' 串行执行的写法
    For Each account In uniqueAccounts().Values
        Await account.getMarketInfoAsync()
        account.LogFinishTask("Geting Market Info", starttime)
        LogEvents($"marketinfo account {account.Exchange} completed")
    Next
    
    ' 如果需要并行执行所有任务
    Dim taskList As New List(Of Task)()
    For Each account In uniqueAccounts().Values
        ' 先收集所有异步任务
        taskList.Add(account.getMarketInfoAsync())
    Next
    ' 等待所有任务完成,恢复时回到主线程
    Await Task.WhenAll(taskList)
    ' 统一执行后续逻辑
    For Each account In uniqueAccounts().Values
        account.LogFinishTask("Geting Market Info", starttime)
    Next
    
  2. 给ContinueWith指定主线程调度器:如果一定要用ContinueWith,需要显式指定调度器,强制后续任务在主线程执行:

    Dim newtask = account.getMarketInfoAsync().ContinueWith(
        Sub() account.LogFinishTask("Geting Market Info", starttime),
        TaskScheduler.FromCurrentSynchronizationContext()
    )
    
  3. 修复LogEvents的线程安全问题:即使在单线程运行,也建议优化方法的安全性,避免后续引入多线程时出问题:

    Private Shared logLock As New Object()
    Public Sub LogEvents(ByVal whatToLog As String, Optional ByVal canRandom As Boolean = True)
        Static logNumber As Integer
        Dim timeStamp As String = CStr(Microsoft.VisualBasic.Now)
        whatToLog = timeStamp & "  " & whatToLog & Microsoft.VisualBasic.vbNewLine
    
        SyncLock logLock
            Debug.Print(whatToLog)
            System.IO.File.AppendAllText("log.txt", whatToLog, defaultEncoding)
        End SyncLock
        ' ... 省略后续代码
    End Sub
    
  4. 检查getMarketInfoAsync内部实现:如果这个方法内部用了Task.Run包裹同步操作,会主动切换到线程池线程。要完全在主线程运行,需要确保它使用真正的异步IO API(比如HttpClient的异步方法),而不是模拟异步。

内容的提问来源于stack exchange,提问作者user4951

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 12:55:25