在foreach循环中使用Task.Run和Synchronous方法

时间:2019-04-29 04:17:14

标签: c# task

我正在尝试找到一种最佳的并行运行同步C#方法的方法。为此,我在foreach循环中选择了Task.Run()。即,将所有同步方法添加到任务中,并使用Task.WhenAll(tasks).Wait()并行执行所有任务。

对不起,如果我使用Task却没有实际用途。我这样做是因为DBContext不是线程安全的,并且我有Mandatory每个事务/数据库记录使用单个DBContext。因此,我认为使方法async无效,因为我在一种方法中执行的所有任务都是相互依赖的。因此,我认为它可以更好地同时运行相同任务的列表。

注意:我运行了该程序,可以看到Tasks并行运行而没有任何问题,并且创建的记录不是连续的。因此,它证实了这些任务可以并行进行。

请帮助我建议我的实施方案是否可以长期运行。

代码

    public void MainMethod()
    {
        foreach (var x in _ListUser)
            tasks.Add(Task.Run(() => Update1Record(x)));
        Task.WhenAll(tasks).Wait();
    }
    public string Update1Record(UserViewModel objUser)
    {
        using (var VibrantDbContext = new VIBRANT())
        using (var AuditDb = new VibrantAuditEntities())
        using (var VibrantTransaction = VibrantDbContext.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
        using (var AuditTransaction = AuditDb.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
        {
            try
            {
                VibrantDbContext.Database.Initialize(force: false);
                AuditDb.Database.Initialize(force: false);
                VibrantDbContext.Configuration.AutoDetectChangesEnabled = false;
                var _ObjUserItem = FillupDateTimeValues(objUser);
                ImportToDB(_ObjUserItem, 0, VibrantDbContext, AuditDb);
                BuildImportLog(objUser, VibrantDbContext, AuditDb);
                VibrantDbContext.SaveChanges();
                AuditDb.SaveChanges();
                VibrantTransaction.Commit();
                AuditTransaction.Commit();
            }
            catch (Exception ex)
            {
                VibrantTransaction.Rollback();
                AuditTransaction.Rollback();
                throw;
            }
        }
        return "S";
    }

2 个答案:

答案 0 :(得分:0)

  

与代码设计相关的观点的结合,何时发生:

foreach (var x in _ListUser)
            tasks.Add(Task.Run(() => Update1Record(x)));
        Task.WhenAll(tasks).Wait();
  • _ListUser拥有1000多个记录,您创建了1000多个Task,无论服务器配置如何,它都会使您的系统瘫痪
  • 尽管Task不能直接映射到线程,但是对于长期运行的方法来说,请求的线程数量众多,远远超出了系统核心可以有效处理的范围,因此,上下文切换和性能大幅下降
  

有哪些选择:

  • Parallel.Foreach相对更好,因为它不允许配置太多线程,但对于数据库处理/ IO调用仍然不是一个好主意。
  • 最好的选择是使用Async - Await,但是挑战在于您的代码是您正在通过同步方法创建任务包装器,这对于Async Await没有帮助,因为它仍然需要后端的后台线程服务器以及类似的问题,只有UI或调用线程将保持响应状态,但是在Async - Await的情况下,我们需要使用真正的Async API来获得最大的收益。
  

以下是建议的修改:

  • 使方法public string Update1Record异步,从而返回Task<string>,我假设DBContext公开了异步API。
  • 如下更改MainMethod

    public async Task MainMethod()
    {
        foreach (var x in _ListUser)
        {
            var localx = x;
            tasks.Add(Update1Record(localx));
        }
        await Task.WhenAll(tasks);
    }
    
      

    请注意localx要捕获局部变量,这是为了避免关闭问题。

此外,为什么异步在这种情况下会更好:

  • 与您的代码相比,异步处理在后台调度要处理的代码,它使用基于硬件的并发性,从而释放软件线程并允许它们处理更多请求,从而提高了系统可伸缩性。返回时,可以使用任何线程来处理结果。

  • 想法不应阻塞和保留像Thread这样的珍贵资源,每个进程都受其限制。调度和接收结果只是整个处理的一小部分,块线程导致浪费重要的计算资源

答案 1 :(得分:-1)

在这种情况下,可以使用PARALLEL foreach。您可以查看此帖子以获取更多信息:

Parallel foreach documentation