【问题标题】:Running Async Foreach Loop C# async await运行异步 Foreach 循环 C# async await
【发布时间】:2017-01-10 16:27:18
【问题描述】:

我正在努力掌握 c# async await 的基本概念。

基本上我所拥有的是一个我需要处理的对象列表,处理包括遍历其属性和连接字符串,然后创建一个新对象(在本例中称为 trellocard)并最终添加一个 trellocard 列表.

迭代需要很长时间,所以我想做的是异步处理多个对象。

我尝试了多种方法,但基本上我想做这样的事情。 (在下面的示例中,我已经删除了处理,只放置了 system.threading.thread.sleep(200)。我等待这不是异步方法,我可以使用 tasks.delay 但关键是我的处理没有有任何异步方法,我只想用多个实例运行整个方法。

    private async Task<List<TrelloCard>> ProcessJobs(IQueryable<IGrouping<CardGrouping, Job>> jobs)
    {
        List<TrelloCard> cards = new List<TrelloCard>();

        foreach (var job in jobs.ToList())
        {
           card = await ProcessCards(job, cards);  // I would like to run multiple instances of the processing
           cards.add(card); //Once each instance is finshed it adds it to the list
        }


  private async Task<TrelloCard> ProcessCards(Job job)
    {
        System.Threading.Thread.Sleep(2000);  //Just for examples sake

        return new TrelloCard();
    }

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    我正在努力掌握 c# async await 的基本概念。

    简单的定义是,Async-Await 是 .Net 并发的一部分,可用于进行多个 IO 调用,并且在处理过程中不会浪费用于计算操作的线程。就像调用数据库、Web 服务、网络调用、文件 IO,所有这些都不需要当前进程线程

    在您当前的情况下,用例是:

    1. 遍历其属性并连接字符串,然后创建一个新对象
    2. 最终添加了一个 trellocard 列表

    这似乎是一个计算绑定操作,除非您正在执行 IO,否则在我看来您正在遍历内存中的对象,对于这种情况,更好的选择是:

    1. Parallel.ForEach,用于并行化内存处理,尽管您需要注意竞争条件,因为给定的内存可以被多个线程访问,因此在写操作期间特别会破坏它,所以至少在当前代码中使用线程安全集合比如来自System.Collections.Concurrent命名空间的ConcurrentBag,或者适合用例而不是List&lt;TrelloCard&gt;,或者你可以考虑关注Thread safe list

    另外请注意,如果您的方法不是默认Async,那么您可能计划将它们包装在Task.Runawait 上,虽然这需要一个线程池线程,但可以使用Async-Await调用

    Parallel.Foreach 用于您的用例的代码(我正在直接替换,您的代码中似乎存在问题,因为 ProcessCards 函数只接受 Job 对象,但您也传递了集合 Cards,它是编译错误):

     private List<TrelloCard> ProcessJobs(IQueryable<IGrouping<CardGrouping, Job>> jobs)
        {
            ConcurrentBag<TrelloCard> cards = new ConcurrentBag<TrelloCard>();
    
            Parallel.ForEach(jobs.ToList(), (job) => 
            {
               card = ProcessCards(job);  // I would like to run multiple instances of the processing
               cards.Add(card); //Once each instance is finshed it adds it to the list
            });
    
               return cards.ToList();
       }
    
      private TrelloCard ProcessCards(Job job)
        {
           return new TrelloCard();
        }
    

    【讨论】:

    • 谢谢!。令人惊讶的是,我一直在网上浏览,你的前 3 行给了我我的灯泡时刻。现在对我来说更有意义了。
    • 欢迎,对于 web 场景,它们就像一个 Ajax 调用,有一些细微的差别,这是特定于 .Net 的
    • @MrinalKamboj 在上述情况下,如果我们需要并行显示一些忙指示符,我们该怎么做?在我的情况下,busyindicator 卡住或显示闪烁效果。有没有人有解决方案或任何提示?
    • @Suchith 应该是可行的,只是像忙指示符这样的 Ui 控件只能在 Ui 线程上执行,而不是线程池线程,检查这个link
    【解决方案2】:

    如果您希望它们并行运行,您可以为每个操作生成一个新任务,然后使用 Task.WhenAll 等待所有操作完成。

    private async Task<List<TrelloCard>> ProcessJobs(IQueryable<IGrouping<CardGrouping, Job>> jobs)
    {
        List<Task<TrelloCard>> tasks = new List<Task<TrelloCard>>();
    
        foreach (var job in jobs)
        {
            tasks.Add(ProcessCards(job));
        }
    
        var results = await Task.WhenAll(tasks);
    
        return results.ToList();
    }
    
    
    private Task<TrelloCard> ProcessCards(Job job)
    {
        return Task.Run(() =>
        {
            System.Threading.Thread.Sleep(2000);  //Just for examples sake
    
            return new TrelloCard();
        });
    }
    

    【讨论】:

    • 这里唯一需要避免的是jobs.ToList(),因为它将不再是远程操作,所有数据都将加载到进程内存中,因此计算绑定操作更合适比 IO 绑定操作。它实际上会破坏 Async-Await 的目的。
    • @MrinalKamboj 是的,使用 async/await 编写此代码(计算绑定)的唯一原因是它是 WPF/WinForms 并且会阻塞 UI。在 ASP.NET 应用程序中,计算密集型工作不应使用 async/await。
    • 即使在 WPF 中也并非如此,您可以在 Threadpool 线程上开始调用,您不需要 Async-Await,除非您计划更新 Ui control,否则它总是需要在Ui thread context 中完成,否则会出现异常(使用 Async-Await 很容易实现,因为没有线程)。理想情况下,执行计算密集型工作的应用程序逻辑不应使用仅用于 IO 调用的 Async-Await。
    • @MrinalKamboj 不,您的陈述是错误的,async/await 不仅适用于 IO 调用。它还旨在防止桌面/移动应用程序中的阻塞。在 WPF 中,async/await 非常适合计算密集型工作。您可以组合 Task.Run 以使用来自 ThreadPool 的线程,并使用 async/await 将延续编组到 UI 线程。如果没有 async/await,您将不得不直接使用 DispatcherTaskScheduler.FromCurrentSynchronizationContext。斯蒂芬克利里有一个很好的答案,你可以阅读链接here
    • 技术概念的工作没有意义,可以通过Stephen cleary 的文章blog.stephencleary.com/2013/11/there-is-no-thread.html 抓住本质(异步操作中没有线程),这就是你热衷于使用它的原因在 WPF 中,它释放了 Ui 线程,并且由于不涉及其他线程,因此上下文在同一上下文中更新。理想情况下,它仅适用于您不想冻结 Ui 的 IO 绑定操作。在等待时,否则等待计算绑定操作总是值得的,因为结果正在处理中。
    【解决方案3】:

    jobs.ToList() 只是在浪费内存。它已经是IEnumerable,所以可以在foreach 中使用。

    ProcessCards 无法编译。你需要这样的东西

        private Task<TrelloCard> ProcessCards(Job job)
        {
            return Task.Run(() =>
            {
                System.Threading.Thread.Sleep(2000);  //Just for examples sake
    
                return new TrelloCard();
            });
        }
    

    现在你想ProcessJobs

    • 为每个作业创建一个 ProcessCards 任务
    • 等待所有任务完成
    • 返回一个 TrelloCard 序列

      private async Task<List<TrelloCard>> ProcessJobs(IQueryable<IGrouping<CardGrouping, Job>> jobs)
      {
          return await Task.WhenAll(jobs.Select(ProcessCards));
      }
      

    【讨论】:

    • +1,正如我之前在其中一个 cmets 中指出的那样,对于 Async-Await 的使用,重要的是不要使用 jobs.ToList(),因为一旦它在本地内存中购买,那么它就可以一个简单的并行处理而不是异步处理
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-23
    • 2019-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多