在前一个异步操作完成之前,在此上下文上启动了第二个操作。使用“等待”来确保

Posted

技术标签:

【中文标题】在前一个异步操作完成之前,在此上下文上启动了第二个操作。使用“等待”来确保【英文标题】:A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure 【发布时间】:2020-01-23 20:04:27 【问题描述】:

我在将 EntityFrmework 6.2 异步方法用于 winform 应用程序时遇到问题。 我们使用自定义类来管理基于单个活动设置的同步或异步 EntityFrmework 访问。当我们使用同步访问时,一切正常。当我们使用异步调用时,我们会遇到这个问题:在前一个异步操作完成之前,在此上下文上启动了第二个操作。使用 'await' 确保在此上下文上调用另一个方法之前已完成任何异步操作。不保证任何实例成员都是线程安全的。 我们已经阅读了很多关于这个问题的帖子,但没有一个可以帮助我们解决我们的问题。

我们知道问题与从同步调用异步方法有关,不幸的是我们无法将调用方法从同步转换为异步。 我们从我们的项目中提取了两种方法来展示所描述的行为

public void AddLoadingRequest(Func<Task> fnAsync, Action fnSync, Action bsa)

    try
    
        Task task = Task.Run(async () => await DoDataSource(fnAsync, fnSync, bsa));
        _lstTask.Add(task);
        if (task.IsFaulted)
            throw task.Exception;
    
    catch (Exception ex)
    
        MessageBox.Show($"Error ex.Message");
    


protected async Task DoDataSource(Func<Task> fnAsync, Action fnSync, Action bsa)

    try
    
        if (_bLoadingFKTableAsync == true)
            await fnAsync();
        else
            fnSync();
    
    catch (Exception ex)
    
        MessageBox.Show(ex.Message, "Loading Error");
        throw;
    

调用方法:

private void btnTest_Click(object sender, EventArgs e)

    NWContext wContext = new NWContext();
    Customers cust = wContext.Customers.FirstOrDefault(w => w.CustomerID == "RATTC");
    BindingSource bsCustomer = new BindingSource();
    BindingSource bsOrders = new BindingSource();
    BindingSource bsCustomerDemographics = new BindingSource();
    bsCustomer.DataSource = cust;
    _bLoadingFKTableAsync = true;
    AddLoadingRequest(
            () => wContext.Entry(cust).Collection(typeof(Orders).Name).LoadAsync(),
            () => wContext.Entry(cust).Collection(typeof(Orders).Name).Load(),
            () =>
            
                bsOrders.SuspendBinding();
                bsOrders.DataSource = cust.Orders;
                bsOrders.ResumeBinding();
            );
    AddLoadingRequest(
            () => wContext.Entry(cust).Collection(typeof(CustomerDemographics).Name).LoadAsync(),
            () => wContext.Entry(cust).Collection(typeof(CustomerDemographics).Name).Load(),
            () =>
            
                bsCustomerDemographics.SuspendBinding();
                bsCustomerDemographics.DataSource = cust.CustomerDemographics;
                bsCustomerDemographics.ResumeBinding();
            );

我们预计 EntityFramework 将在异步模式下加载对象的两个子集合,但我们遇到错误:在前一个异步操作完成之前在此上下文上启动了第二个操作。使用 'await' 确保在此上下文上调用另一个方法之前已完成任何异步操作。不保证任何实例成员都是线程安全的。

【问题讨论】:

那么问题是什么?您专门对 AddLoadingRequest 进行了 2 次调用,这两个调用都使用相同的上下文启动了异步操作。在开始第二个之前,您不会 (a) 等待第一个完成。 我们正在尝试从 AddLoadingRequest 同步方法中调用“DoDataSource”异步方法;我们报告了错误,因为我们无法将调用方法 AddLoadingRequest 从同步更改为异步。问题是:我们如何从现有的同步方法中调用两个或多个异步 EF 请求? 【参考方案1】:

问题是:我们如何从现有的同步方法中调用两个或多个异步 EF 请求?

您需要两个或更多数据库上下文。每个人一次只能有一个请求。

附带说明,我强烈建议您将方法更改为async。像这样跳过await 是一种“一劳永逸”的方式,它有两个主要问题:

    您的代码无法知道操作何时完成。现在我可以重新使用这个数据库上下文,因为它已经完成了那个数据库操作吗?知道所有数据库更新都已应用,我可以安全退出应用程序吗?这些问题无法用“一劳永逸”的代码来回答。 您的代码无法知道操作是否成功。使用“一劳永逸”,您的代码必须假设它有效。

【讨论】:

我担心唯一的解决方案是获取多个上下文。非常感谢您快速而详尽的回复。 @FabioBorghi 如果这解决了您的问题,请将答案标记为解决方案。

以上是关于在前一个异步操作完成之前,在此上下文上启动了第二个操作。使用“等待”来确保的主要内容,如果未能解决你的问题,请参考以下文章

EF 错误:在前一个异步操作完成之前在此上下文上启动了第二个操作

Blazor:在前一个操作完成之前在此上下文上启动了第二个操作

UserManager 错误“在前一个操作完成之前在此上下文上启动了第二个操作”

Entity Framework Core:在前一个操作完成之前在此上下文上启动了第二个操作

asp.net core 在前一个操作完成之前在此上下文上启动了第二个操作

在此上下文中启动了第二个操作