C#异步函数-是否等待立即在新线程上启动任务?

问题描述

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

public LanDeviceInfo GetLanDBData(LanDeviceInfo device) 

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

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

然后,在用户界面上,单击按钮,就在循环内使用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返回类型的async修饰符定义此函数将使编译器自动生成一个任务,只要调用GetLanDBDataAsync()就会返回该任务。然后从await GetLanDBDataAsync()调用GenerateCommissioningFile()自动使其在新线程中运行,而不会阻塞UI。当在UI线程上运行的GetLanDBData()已经在等待异步功能时,为什么必须手动创建一个任务来运行GetLanDBDataAsync()并在GenerateCommissioningFile()中等待它?我觉得我真的很想念这里;)

谢谢!

解决方法

此功能:

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);
}

因此,确实编译器生成了一个任务并返回了该任务,但不是您期望的那样。整个方法在调用方(UI)线程上同步运行,因此以与GetLanDBData相同的方式对其进行阻止。

任务基本上代表了一些正在进行的工作(或什至已经完成,如上面的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返回的任务代表此挂起的操作。

  3. 现在,如果从Task.Run返回的任务尚未完成(尚未完成),请将我们的taskResult(代表整个操作)返回给调用者。

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