正确使用异步/等待多个任务到数据库

本文关键字:任务 数据库 等待 异步 | 更新日期: 2023-09-27 18:31:27

我有一个简单的场景,但我想知道我的方法是否正确,是最好选择一个任务来保存我失败的订单,还是我可以启动并启动多个任务并等待它们全部完成。在连接到 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();
    }

正确使用异步/等待多个任务到数据库

连接到 Db 和保存实体时,此方案的正确方法是什么。

一般来说,您应该:

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

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

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

作为旁注:根据"保存"的复杂性,连续执行每个"保存"可能更可靠。这是因为如果存在由并行任务引起的任何数据库错误(如约束违规),那么如果并行执行(即在随机时间)执行,将很难重现。

new Task(...)不会

启动任务。启动它们不是Task.WhenAll的责任。几乎永远不应该使用任务 ctor。

使用Task.Run .

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

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

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

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

有关进一步阅读,请参阅此处:实体框架映射异常:类型'XXX已被映射多次