【问题标题】:Cleanest async version of this foreach?这个 foreach 最干净的异步版本?
【发布时间】:2021-06-10 20:05:51
【问题描述】:

我在 AWS Lambda 中有一个方法,它接收完整的项目列表。它处理这些项目,然后将它们插入到 Dynamo DB 表中。处理或插入数据库的顺序无关紧要。

我正在寻找最线程安全且易于理解的方式来加快运行速度;通过使用async await 或其他(可能是并行的?)操作。

我在想Parallel.ForEach(),但这似乎有点沉重。有没有更简单、更明显的方法?

private async Task<int> LoadAutocomplete(IList<Item> resp)
{
    var client = new AmazonDynamoDBClient();

    foreach (var item in resp)
    {
        var request = new PutItemRequest
        {
            TableName = EnvironmentHelper.DynamoTableName,
            Item = new Dictionary<string, AttributeValue>()
            {
                { "LANGUAGE", new AttributeValue { S = item.LANGUAGE }},
                { "COUNTRY", new AttributeValue { S = item.COUNTRY }}
            }
        };
        await client.PutItemAsync(request);

        System.Threading.Thread.Sleep(100);
    }
}

选项 1

private async Task<int> LoadAutocomplete(IList<Item> resp)
{
    var client = new AmazonDynamoDBClient();

    Parallel.ForEach(resp, async item =>
    {
        var request = new PutItemRequest
        {
            TableName = EnvironmentHelper.DynamoTableName,
            Item = new Dictionary<string, AttributeValue>()
            {
                { "LANGUAGE", new AttributeValue { S = item.LANGUAGE }},
                { "COUNTRY", new AttributeValue { S = item.COUNTRY }}
            }
        };
        await client.PutItemAsync(request);
    }
}

选项 2 导致编译器抱怨 LoadAutoComplete 方法“缺少等待操作符并且将同步运行”。

选项 2 正如@jamesfaix 建议的那样

private async Task<int> LoadAutocomplete(IList<Item> resp)
{
    var client = new AmazonDynamoDBClient();
    
    var tasks = items.Select(x => DoSomethingAsync(client, x)).ToList();
    
    await Task.WhenAll(tasks);
}

private DoSomething(AmazonDynamoDBClient client, Item item)
{
    var request = new PutItemRequest
    {
        TableName = EnvironmentHelper.DynamoTableName,
        Item = new Dictionary<string, AttributeValue>()
        {
            { "LANGUAGE", new AttributeValue { S = item.LANGUAGE }},
            { "COUNTRY", new AttributeValue { S = item.COUNTRY }}
        }
    };
    
    await client.PutItemAsync(request);
}

选项 + @martin 建议

"Use Parallel.For in batches in dotnet core" 帖子确实回答了我的问题,但我选择了@jamesfaix 发布的the answer,因为它大大改进了我的代码。

【问题讨论】:

  • 不要在异步代码中使用Sleep。使用await Task.Delay
  • 啊,是的。我实际上已经删除了那条线,但忘记了。无论如何,我可能会被它搞砸,因为我似乎记得需要该行来确保数据库插入正常执行。
  • 在您的选项 1 和选项 2 之间,我会更关心行为差异而不是句法清洁度。
  • 您可以直接拒绝“选项 1”。 Parallel.ForEachis not async friendly.
  • 您在a comment 中提到“数据库插入正常执行”,在操作之间添加了 100 毫秒的延迟。

标签: c# .net-core foreach async-await


【解决方案1】:

以下是我将首先进行的一些基本更改。您可能还可以从那里进行其他改进。

  1. 避免在异步代码中使用Thread.SleepTask.Delay 是异步等效项。
  2. 在等待之前创建许多任务。如果可以,运行时将尝试同时运行一些。
private async Task<int> LoadAutocomplete2(IList<Item> resp)
{
    var client = new AmazonDynamoDBClient();

    var tasks = resp.Select(async item =>
    {
        var request = new PutItemRequest
        {
            TableName = EnvironmentHelper.DynamoTableName,
            Item = new Dictionary<string, AttributeValue>()
            {
                { "LANGUAGE", new AttributeValue { S = item.LANGUAGE }},
                { "COUNTRY", new AttributeValue { S = item.COUNTRY }}
            }
        };

        var result = await client.PutItemAsync(request);
        await Task.Delay(100);
        return result;
    })
    .ToList(); // Make sure to materialize the IEnumerable!

    await Task.WhenAll(tasks);
}

【讨论】:

  • 谢谢@jamesfaix - 这已将执行时间从 1:20 缩短到 0:03 :)
  • 为什么实现tasks IEnumerable 很重要?
  • 是不是因为在WhenAll检查过程中意外枚举列表多次会导致列表中的项目重新处理? (这个解释是我从这篇文章中推导出来的:stackoverflow.com/a/60447378/71376
  • 这是我的想法。再读一遍,没有ToList 可能没问题,因为WhenAll 是唯一迭代集合的东西,但如果集合有任何其他用途,我会谨慎。多次迭代错误很容易潜入。
猜你喜欢
  • 2012-10-06
  • 1970-01-01
  • 1970-01-01
  • 2010-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多