【问题标题】:Correct usage of Async/Await for Multiple Tasks To Db正确使用 Async/Await 对 Db 执行多个任务
【发布时间】:2015-01-20 14:58:36
【问题描述】:

我有一个简单的场景,但我想知道我的方法是否正确,最好选择一个任务来保存我失败的订单,还是我可以启动并启动多个任务并等待它们全部完成完全的。在连接到 Db 和保存实体时,这种情况下的正确方法是什么。

我已经有一个基于单个任务的版本,可以将一个实体保存到数据库中。

    public async static Task SaveOrdersAsync(OrderService oService, OrderItemService oiService, IEnumerable<OrderTemplate> toSaveList, IUnitOfWork uow, IProgress<string> progress)
    {
        var toSave = toSaveList as IList<OrderTemplate> ?? toSaveList.ToList();
        var tasks = new Task[toSave.Count()];

        for (var i = 0; i < tasks.Length; i++)
        {
            var i1 = i;

            tasks[i] = new Task(() => SaveToDb(oService, oiService, toSave.ElementAt(i1), uow), TaskCreationOptions.PreferFairness);

            var message = string.Format("- Order: {0} has been resaved.\n", toSave.ElementAt(i1).Order.FriendlyId);

            if (progress != null)
                progress.Report(message);
        }

        await Task.WhenAll(tasks);
    }

目前,我已经对上述内容进行了测试,并且认为由于进度条一直在循环,因此任务尚未开始。我的假设是 Task.WhenAll 应该为我开始我的任务 - 这就是我的想法?

或者应该在循环中使用它:

      tasks[i] = Task.Run(() => SaveToDb(oService, oiService, toSave.ElementAt(i1), uow));

我想我很接近,只是希望有人告诉我这样做是否正确。

反馈合并版本:

    public async static Task SaveOrdersAsync(OrderService oService, OrderItemService oiService, IEnumerable<OrderTemplate> toSaveList, IUnitOfWork uow, IProgress<string> progress)
    {   
        var saveList = toSaveList as IList<OrderTemplate> ?? toSaveList.ToList();
        var saveTask = Task.Run(() =>
        {
            foreach (var ot in saveList)
            {
                SaveToDbBatch(oService, oiService, ot);

                var message = string.Format("- Order: {0} has been resaved.\n", ot.Order.FriendlyId);
                if (progress != null)
                    progress.Report(message);
            }
        });

        await saveTask;
        await Cache.UoW.SaveAsync();
    }

【问题讨论】:

    标签: c# .net task-parallel-library async-await .net-4.5


    【解决方案1】:

    在连接到 Db 和保存实体时,这种情况下的正确方法是什么。

    一般来说,你应该:

    1. 如果可能,批量保存。换句话说,调用一个方法来同时更新多条记录。例如,EF 有SaveChangesAsync
    2. 为您的数据库使用自然异步 API,而不是 Task.Run(或者 - 更糟 - 任务构造函数)。例如,EF 有SaveChangesAsync

    【讨论】:

    • 干杯,斯蒂芬希望你能回复。好吧,酷看起来我有一些改变要做。 =)
    【解决方案2】:

    是的,创建任务不会启动它是正确的。调用 Task.Run(...) 是更好的选择。

    但是,更好的选择是使用从您对 ExecuteAsync(...) 的调用返回的任务并等待它。这是因为 ExecuteAsync 任务是 IO 任务而不是线程,所以它的执行方式不同,并且不会用完线程池线程。

    附带说明:根据“保存”的复杂性,连续执行每个“保存”可能更可靠。这是因为如果并行任务导致任何数据库错误(如违反约束),那么如果它们并行执行(即在随机时间)将非常难以重现。

    【讨论】:

    • 太好了,我会把这一切整合到一个任务中,我没想到。
    【解决方案3】:

    new Task(...) 不启动任务。 Task.WhenAll 不负责启动它们。 Task ctor 几乎不应该被使用。

    使用Task.Run

    【讨论】:

      【解决方案4】:

      似乎将这归结为我在更新中发布的一项任务有效,它还解决了一个我认为我会在这里提出的附带问题,以防其他人热衷于追求我的原始方法。但我同意@jaytre,根据您保存的复杂性和要保存的对象,最好连续进行每次保存以进行错误处理 - 但这取决于您。

      因此,如果您采用我原来的方法,您可能会遇到此错误:

      一个 EdmType 不能多次映射到 CLR 类。 EdmType 'FrootPipe.Data.Order' 被多次映射。

      这基本上归结为锁定/同步问题 - 因此不同的任务或多或少地同时访问模型,所有任务都试图将失败的订单重新添加回数据模型。所以我的场景的错误有点难以分辨,但一些谷歌搜索将我带到下面。

      更多阅读请看这里:Entity framework MappingException: The type 'XXX has been mapped more than once

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-25
        • 2019-07-01
        相关资源
        最近更新 更多