在 Select LINQ 方法中使用 Task.Run()

use Task.Run() inside of Select LINQ method

假设我有以下代码(仅用于学习目的):

static async Task Main(string[] args)
{
    var results = new ConcurrentDictionary<string, int>();

    var tasks = Enumerable.Range(0, 100).Select(async index =>
    {
        var res = await DoAsyncJob(index);
        results.TryAdd(index.ToString(), res);
    });

    await Task.WhenAll(tasks);

    Console.WriteLine($"Items in dictionary {results.Count}");
}

static async Task<int> DoAsyncJob(int i)
{
    // simulate some I/O bound operation
    await Task.Delay(100);

    return i * 10;
}

我想知道如果我这样做会有什么不同:

var tasks = Enumerable.Range(0, 100)
    .Select(index => Task.Run(async () =>
    {
        var res = await DoAsyncJob(index);
        results.TryAdd(index.ToString(), res);
    }));

我在这两种情况下得到了相同的结果。但是代码的执行是否类似?

是的,这两种情况的执行方式相似。实际上,它们的执行方式完全相同。

Task.Run 用于在线程池线程中执行 CPU 绑定的同步操作。由于您 运行ning 的操作已经是异步的,因此使用 Task.Run 意味着您在线程池线程中将工作安排到 运行,而该工作仅仅是 开始 一个异步操作,然后几乎立即完成,然后继续执行它必须执行的任何异步工作,而不会阻塞该线程池线程。因此,通过使用 Task.Run 您正在等待线程池中的调度工作,但实际上并没有做任何有意义的工作。您最好只在当前线程中启动异步操作。

唯一的例外是如果 DoAsyncJob 实施不当并且由于某种原因实际上不是异步的,与其名称和签名相反,并且实际上在返回之前做了很多同步工作。但如果它这样做了,你应该只修复那个有问题的方法,而不是使用 Task.Run 来调用它。

附带说明一下,没有理由让 ConcurrentDictionary 在这里收集结果。 Task.WhenAll returns 您执行的所有任务的结果集合。就用那个。现在你甚至不需要一个方法来包装你的异步方法并以任何特殊的方式处理结果,进一步简化代码:

var tasks = Enumerable.Range(0, 100).Select(DoAsyncJob);

var results = await Task.WhenAll(tasks);

Console.WriteLine($"Items in results {results.Count}");