将异步方法传递给Parallel.ForEach

时间:2018-11-27 09:59:28

标签: c# async-await parallel.foreach

我在读this post中有关Parallel.ForEach的内容时说:“ Parallel.ForEach与传递异步方法不兼容。”

因此,要检查一下我是否编写了这段代码:

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

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

    Console.ReadLine();
}

static async Task<int> DoAsyncJob(int i)
{
    Thread.Sleep(100);
    return await Task.FromResult(i * 10);
}

此代码同时填写results字典。

顺便说一句,我创建了一个ConcurrentDictionary<string, int>类型的字典,因为如果以ConcurrentDictionary<int, int>的形式在调试模式下浏览其元素时,我发现元素是按键排序的,所以我认为优雅因此被添加了。

所以,我想知道我的代码是否有效?如果它“与传递异步方法不兼容”,为什么运行良好?

3 个答案:

答案 0 :(得分:4)

async 方法是启动并返回Task的方法。

您的代码在这里

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

并行运行异步方法100次。也就是说,它使任务 creation 而不是整个任务并行化。到ForEach返回时,您的任务已在运行,但不一定完成。

您的代码之所以有效,是因为DoAsyncJob()实际上不是异步的-您的任务在返回时就完成了。 Thread.Sleep()是一种同步方法。 Task.Delay()是它的异步等效项。

了解CPU-bound and I/O-bound operations之间的区别。正如其他人已经指出的那样,并行度(和Parallel.ForEach)适用于CPU绑定的操作,异步编程是不合适的。

答案 1 :(得分:3)

此代码仅由于DoAsyncJob并不是一个异步方法而起作用。 async不会使方法异步工作。等待Task.FromResult返回的已完成任务也是同步的。 async Task Main不包含任何异步代码,这会导致编译器警告。

一个示例演示Parallel.ForEach不适用于异步方法的示例应调用一个真正的异步方法:

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

        Parallel.ForEach(Enumerable.Range(0, 100), async index =>
        {
            var res = await DoAsyncJob(index);
            results.TryAdd(index.ToString(), res);
        });  
        Console.WriteLine($"Items in dictionary {results.Count}");
    }

    static async Task<int> DoAsyncJob(int i)
    {
        await Task.Delay(100);
        return i * 10;
    }

结果将是

Items in dictionary 0

Parallel.ForEach没有接受Func<Task>的重载,它仅接受Action个委托。这意味着它无法等待任何异步操作。

async index被接受,因为它隐式为async void delegate。就Parallel.ForEach而言,它只是一个Action<int>

结果是Parallel.ForEach触发了100个任务,并且从不等待它们完成。这就是为什么在应用程序终止时字典仍然为空的原因。

答案 2 :(得分:1)

如果您已经有异步工作,则不需要Parallel.ForEach

static async Task Main(string[] args)
{

    var results = await new Task.WhenAll(
        Enumerable.Range(0, 100)
        Select(i => DoAsyncJob(I)));

    Console.ReadLine();
}

关于异步作业,您可以一路异步:

static async Task<int> DoAsyncJob(int i)
{
    await Task.Delay(100);
    return await Task.FromResult(i * 10);
}

更好:

static async Task<int> DoAsyncJob(int i)
{
    await Task.Delay(100);
    return i * 10;
}

或根本不:

static Task<int> DoAsyncJob(int i)
{
    Thread.Sleep(100);
    return Task.FromResult(i * 10);
}