C# 异步函数 - await 会立即在新线程上启动任务吗?

C# asynchronous functions - does await immediately start a task on a new thread?

我正在重构一些用于异步操作的 C# 代码,但恐怕我没有深入了解 C# 等待指令的含义。我有一个方法可能会进行一些可能冗长的处理,并且需要连续 运行 200 次:

public LanDeviceInfo GetLanDBData(LanDeviceInfo device) 

然后我创建了一个使用它的异步版本:

public async Task<LanDeviceInfo> GetLanDBDataAsync(LanDeviceInfo device)
{
    var deviceInfo = await Task.Run(() => GetLanDBData(device));
    return deviceInfo;
}

并且,在 UI 上,单击按钮后,我 运行 在循环中使用 await 关键字将其

    private async void GenerateCommissioningFile()
    {
            foreach (VacFwPLCInfo thisPLC in filteredPLCList)
            {
                try
                {
                    plcCount++;
                    buttonGenerateComFile.Text = $"LanDB ({plcCount}/{filteredPLCList.Count})";
                    await lanDB.GetLanDBDataAsync(thisPLC);
                }
                catch (Exception ex)
                {
                    thisPLC.Error = true;
                }
            }
    }

所有这些工作正常,函数以异步方式调用,我的 UI 没有被阻塞。

现在,我不明白的是为什么如下定义 GetLanDBDataAsync 函数编译正常但不起作用并阻塞 UI 线程:

public async Task<LanDeviceInfo> GetLanDBDataAsync(LanDeviceInfo device)
{
    return GetLanDBData(device);
}

据我了解,这也应该有效。使用带有 Task return 类型的 async 修饰符定义此函数将使编译器自动生成一个任务,只要调用 GetLanDBDataAsync() 就会被 returned。然后从 GenerateCommissioningFile() 调用 await GetLanDBDataAsync() 会自动在新线程中使其成为 运行 而不会阻塞 UI。为什么我必须手动创建一个任务到 运行 GetLanDBData() 并在 GetLanDBDataAsync() 内等待 GenerateCommissioningFile(),运行 在 UI 线程上,已经在等待异步函数?我觉得我真的在这里错过了什么 ;)

谢谢!

对于此函数:

public async Task<LanDeviceInfo> GetLanDBDataAsync(LanDeviceInfo device)
{
    return GetLanDBData(device);
}

编译器应发出警告:“此异步方法缺少 'await' 运算符,将 运行 同步”。编译器会将此方法转换为如下形式:

public Task<LanDeviceInfo> GetLanDBDataAsync(LanDeviceInfo device)
{
    var result = GetLanDBData(device);
    return Task.FromResult(result);
}

因此编译器确实生成了一个任务并 return 编辑了它,但不是以您期望的方式。整个方法 运行 在调用者 (UI) 线程上同步,因此以与 GetLanDBData 相同的方式阻塞它。

Task 基本上代表一些正在进行的工作(甚至已经完成,如上图 Task.FromResult 所示),能够检查所述工作的状态,并在工作完成(或失败)时收到通知.不一定跟线程有关系。

await someTask 非常粗鲁地意味着 - 如果 someTask 尚未完成 - 然后在稍后 someTask 实际完成时执行该方法的其余部分。它不会启动任何新任务,也不会创建任何新线程。

您的工作版本:

public async Task<LanDeviceInfo> GetLanDBDataAsync(LanDeviceInfo device)
{
    var deviceInfo = await Task.Run(() => GetLanDBData(device));
    return deviceInfo;
}

非常粗糙的意思是-

  1. GetLanDBDataAsync 方法中创建一个代表整个操作的任务。让我们命名为 taskResult.

  2. 队列 GetLanDBData 将在线程池上执行(因为那是 Task.Run 的文档所说的,而不仅仅是因为“这是一项任务”)。从 Task.Run 编辑 return 的任务表示此待处理操作。

  3. 现在,如果从 Task.Run 编辑的任务 return 尚未完成(它没有) - return 我们的 taskResult (代表整个操作)返回给调用者。

  4. 一段时间后,从 Task.Run 编辑的任务 return 完成时 - 我们将执行其余代码。在这种情况下,Task.Run 的结果只是转发给我们的 taskResult,因为其余代码只是 return deviceInfo.