use Task.Run() inside of Select LINQ method

2019-07-26 02:25发布

问题:

Suppose I have the following code (just for learninig purposes):

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

I want to know what will be the difference if I make it as follows:

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

I get the same results in both cases. But does code executes similarly?

回答1:

Task.Run is there to execute CPU bound, synchronous, operations in a thread pool thread. As it is the operation you're running is already asynchronous, so using Task.Run means you're scheduling work to run in a thread pool thread, and that work is merely starting an asynchronous operation, which then completes almost immediately, and goes off to do whatever asynchronous work it has to do without blocking that thread pool thread. So by using Task.Run you're waiting to schedule work in the thread pool, but then not actually don't any meaningful work. You're better off just starting the asynchronous operation in the current thread.

The only exception would be if DoAsyncJob were implemented improperly and for some reason wasn't actually asynchronous, contrary to its name and signature, and actually did a lot of synchronous work before returning. But if it is doing that, you should just fix that buggy method, rather than using Task.Run to call it.

On a side note, there's no reason to have a ConcurrentDictionary to collect the results here. Task.WhenAll returns a collection of the results of all of the tasks you've executed. Just use that. Now you don't even need a method to wrap your asynchronous method and process the result in any special way, simplifying the code further:

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

var results = await Task.WhenAll(tasks);

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


回答2:

Yes, both cases execute similarly. Actually, they execute in exactly the same way.