【问题标题】:Can not use await inside a Parallel.Foreach . error :- The 'await' operator can only be used within an async lambda expression不能在 Parallel.Foreach 中使用 await。错误:- 'await' 运算符只能在异步 lambda 表达式中使用
【发布时间】:2016-06-28 13:36:19
【问题描述】:

我的 asp.net mvc-5 Web 应用程序中有以下方法:-

public async Task <List<Details>> Get()
{
    Parallel.ForEach(resourcesinfo.operation.Details,new ParallelOptions { MaxDegreeOfParallelism = 20 }, (c) =>
    {
        ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
        using (WebClient wc = new WebClient()) 
        {
            string url = currentURL + "resources/" + c.RESOURCEID + "/accounts?AUTHTOKEN=" + pmtoken;
            string tempurl = url.Trim();

            var json =  await wc.DownloadStringTaskAsync(tempurl);
            resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
        }
        //code goes here

但我收到此错误:-

“await”运算符只能在异步 lambda 表达式中使用。考虑用 'async' 修饰符标记这个 lambda 表达式

请问有人可以推荐吗?

【问题讨论】:

  • Parallel.Foreach 不是为并行异步操作而设计的;它旨在并行化同步操作。你根本不应该使用它。
  • 啊,正如@scottchamberlain 在我现在删除的答案中提到的那样,你不能。 Parallel.ForEach 没有接受任务的重载。我想这里的主要问题是您正在混合来自两种不同范式(Tasks 和 Pfx)的异步操作模式。如果您正在处理async 的东西,那么最好全力以赴。这是一些类似 C# 的伪代码,它说明了:Task.WaitAll(resourcesinfo.operation.Details.Select(x =&gt; CallAsyncMethod(x)).ToArray())
  • 有许多有效的模式。这是一个起点。 weblogs.asp.net/cibrax/await-whenall-waitall-oh-my TPL 库听起来也不错。

标签: c# .net asp.net-mvc .net-4.5 parallel.foreach


【解决方案1】:

Parallel.ForEeach 不是为使用异步函数而设计的,您需要使用更现代的类,例如TPL Dataflow 中的类。您可以通过将 NuGet 包安装到您的项目Microsoft.Tpl.Dataflow 来获得它。您可以将以前的代码重新创建为

private const int MAX_PARALLELISM = 20

public async Task <List<Details>> Get()
{
    var block = new ActionBlock<Entry>(async (c) => 
        {
            ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
            using (WebClient wc = new WebClient()) 
            {
                string url = currentURL + "resources/" + c.RESOURCEID + "/accounts?AUTHTOKEN=" + pmtoken;
                string tempurl = url.Trim();

                var json =  await wc.DownloadStringTaskAsync(tempurl);
                resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
            }
            //code goes here
        }
        ,new ExecutionDataflowBlockOptions 
                   { 
                         MaxDegreeOfParallelism = MAX_PARALLELISM
                   });

    foreach(var entry in resourcesinfo.operation.Details)
    {
        await block.SendAsync(entry);
    }

    block.Complete();
    await block.Completion;

    //More code here    
}

经过一番思考,这里有一个稍微复杂一点的版本,它完成了从读取记录到从Get()返回结果的整个管道

private const int MAX_PARALLELISM = 20

public async Task<List<Details>> Get()
{
    List<Details> result = new List<Details>();

    var getRecordBlock = new TransformBlock<Entry, ResourceAccountListInfo>(async (c) =>
        {
            ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
            using (WebClient wc = new WebClient())
            {
                string url = currentURL + "resources/" + c.RESOURCEID + "/accounts?AUTHTOKEN=" + pmtoken;
                string tempurl = url.Trim();

                var json = await wc.DownloadStringTaskAsync(tempurl);
                resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
            }
            return resourceAccountListInfo;
        }
        , new ExecutionDataflowBlockOptions
        {
            MaxDegreeOfParallelism = MAX_PARALLELISM
        });

    //Defaults to MaxDegreeOfParallelism = 1
    var addToListBlock = new ActionBlock<ResourceAccountListInfo>(info =>
    {
        Details detail = TurnResourceAccountListInfoInToDetails(info);
        result.Add(detail);
    });

    getRecordBlock.LinkTo(addToListBlock, new DataflowLinkOptions { PropagateCompletion = true});

    foreach (var entry in resourcesinfo.operation.Details)
    {
        await getRecordBlock.SendAsync(entry);
    }

    getRecordBlock.Complete();
    await addToListBlock.Completion;

    return result;
}

【讨论】:

  • 对于任何阅读本文的人,您知道比“异步阻塞”更好的术语吗?我不喜欢在答案中使用那个短语。
  • TPL 数据流是 .Net 的一部分。它只是不随 .net 安装程序一起提供。 Microsoft 正在向 .net 迁移该模型,它不是“第 3 方”,您也没有“安装”任何东西,只需包含 NuGet 包即可。
  • 如果你只是要使用同步方法,只需执行wc.DownloadString(tempurl) 就不要涉及异步。然而,为了获得最佳性能,您真的应该将 NuGet 包添加到您的解决方案中。
  • @RenéVogt 我不知道你是否还在看这个答案,但你可能想看看我之前的评论,它说明了为什么在 Parallel.Foreach 中做 .Result 可能很危险.
  • 这就是将数据推入getRecordBlock的原因,它取代了旧Parallel.ForEach的第一个参数
猜你喜欢
  • 2016-02-08
  • 2014-01-02
  • 1970-01-01
  • 2014-11-04
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 2020-05-26
  • 1970-01-01
相关资源
最近更新 更多