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

3

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

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

我在两种情况下获得了相同的结果,但代码执行方式是否相似呢?

4
Task.Run 调度线程池上的工作。这意味着您正在将工作推送到线程池线程,但它们会立即返回,因为它们唯一的工作是启动 DoAsyncJob。在这里使用 Task.Run 是无用的。只有当您将 IO 绑定的工作替换为 CPU 绑定的工作时,才需要使用 Task.Run - FCin
1
你正在创建包装“Tasks”的“Tasks”(幸运的是,“Task.Run”知道你实际上关心内部任务并将其解包)。作为实现细节,异步方法创建代表它们最终完成的“Task”并提前返回。你为什么觉得需要在单独的“Task”中包装调用该方法呢? - Damien_The_Unbeliever
2个回答

4

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

唯一的例外情况是,如果DoAsyncJob未能正确实现,由于某种原因它实际上不是异步的(与其名称和签名相反),并且在返回之前实际上执行了很多同步工作。但如果是这样,请修复这个有缺陷的方法,而不是使用Task.Run调用它。

顺便说一下,在这里收集结果的时候,没有理由使用ConcurrentDictionaryTask.WhenAll返回所有执行的任务结果的集合。只需使用它即可。现在,您甚至不需要一个方法来包装您的异步方法并以任何特殊的方式处理结果,这进一步简化了代码:

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

var results = await Task.WhenAll(tasks);

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

-2

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接