c# - 使用 LINQ Lambda 选择的 ASP.Net MVC 异步导致“在前一个操作完成之前,在此上下文上启动了第二个操作。”
问题描述
我一直在与被证明难以追踪涉及 LINQ Lambda 选择中的等待任务的问题作斗争。
注意:下面的代码已被简化并使用演示命名,即 Town、Region 等。但工作流程与出现问题的原始代码相同。
- 单步执行 VS 调试器时,代码运行完美。
- 但是,如果一个城镇 > 1 级并且调用该函数以非调试模式运行时
await this.CheckTownSpecialProperty(t)
,我最终会得到A second operation was started on this context before a previous operation completed.
. - 只有在非调试模式下运行时才会引发此错误。在调试模式下它工作正常,大概是因为步进防止了上下文的并发冲突。
- 我已阅读有关延迟执行导致的 LINQ Lambda 选择中异步危险的 Microsoft 警告。https://docs.microsoft.com/en-us/dotnet/csharp/async
- 我基本上只是想知道为什么在这种情况下会发生这种情况,即使使用了控制上下文的 using 块。
public async Task<IEnumerable<TownSearchOutputDto>> GetTowns(TownSearchInputDto input)
{
var numResults = 25;
var query = _townRepository.GetAll()
.Include(t => t.Tests)
.Include(t => t.Region.Select(s => s.SubRegion))
.Include(t => t.Province); // Eager load relations.
// Filter based on CountyId.
if (input.CountyId > 0)
{
query = query.Where(t => t.CountyId == input.CountyId);
}
var townList = await query
.OrderBy(t => t.Name)
.Take(numResults)
.ToListAsync();
IEnumerable<Task<TownSearchOutputDto>> tasks = townList.Select(async t => {
var townLevel = this.GetTownLevel(t);
bool hasSomeSpecialTownProperty = false;
if (townLevel.Key > 1)
{
hasSomeSpecialTownProperty = await this.CheckTownSpecialProperty(t);
}
return new TownSearchOutputDto
{
Id = t.Id,
CountyId = t.CountyId,
RegionName = t.Region.Name,
SubRegionName = t.SubRegion.Name,
ProvinceName = t.Province.Name,
TownLevelOrder = townLevel.Key,
TownLevelName = townLevel.Value,
HasSomeSpecialTownProperty = hasSomeSpecialTownProperty,
Disabled = townLevel.Key > 2 || t.Tests.Any(a => a.TownId == t.Id)
};
}).ToList();
var towns = await Task.WhenAll(tasks);
return towns;
}
private KeyValuePair<int, string> GetTownLevel(Town town)
{
if (!string.IsNullOrWhiteSpace(town.Tiny))
{
return new KeyValuePair<int, string>(3, "Tiny");
}
else if (!string.IsNullOrWhiteSpace(town.Small))
{
return new KeyValuePair<int, string>(2, "Small");
}
else if (!string.IsNullOrWhiteSpace(town.Big))
{
return new KeyValuePair<int, string>(1, "Big");
}
return new KeyValuePair<int, string>(-1, null);
}
private async Task<bool> CheckTownSpecialProperty(Town town)
{
// If no town is found then we cannot continue.
if (town is null)
{
throw new UserFriendlyException("Town not found. Cannot continue!");
}
var townLevel = this.GetTownLevel(town);
if(townLevel.Key == 1) // This is big town level, exit early. We are just testing against smaller towns.
{
return false;
}
// Find any big town of the same name also being tested.
// This is where the issue occurs.
// In debug mode all works well but when running freely, I get:
// "A second operation was started on this context before a previous operation completed."
var townList = await _townRepository.GetAll()
.Include(t => t.Tests)
.Include(t => t.Region.Select(s => s.SubRegion))
.Include(t => t.Province); // Eager load relations.
.Where(t => t.Id != town.Id
&& t.Region.Name == town.Region.Name
&& t.Name == town.Name
&& t.SubRegion.Name == town.SubRegion.Name
&& t.Tests.Any(a => a.TownId == t.Id))
.ToListAsync();
// If we have found a big town also being tested return true.
if (townList.Any(t => this.GetTownLevel(t).Key == 1))
{
return true;
}
return false;
}
我试图解决这个问题的事情
- 制作
CheckTownSpecialProperty()
同步。但这违背了 GetTowns 一开始就异步的目的,它实际上只是为了让我知道该功能工作正常。 - 我尝试添加 using 块并手动控制上下文实例。我推测这会起作用,因为将为每个任务重新创建上下文实例。
- 但这会导致:
"The operation cannot be completed because the DbContext has been disposed."
- 我也在 GetTowns() 函数中尝试了相同的方法,并
"The operation cannot be completed because the DbContext has been disposed."
引发了相同的错误。
- 但这会导致:
List<Town> townList = null;
using (var context = _abpContext.GetDbContext())
{
townList = await context.Towns
.Include(t => t.Tests)
.Include(t => t.Region.Select(s => s.SubRegion))
.Include(t => t.Province); // Eager load relations.
.Where(t => t.Id != town.Id
&& t.Region.Name == town.Region.Name
&& t.Name == town.Name
&& t.SubRegion.Name == town.SubRegion.Name
&& t.Tests.Any(a => a.TownId == t.Id))
.ToListAsync();
}
- 通过 . 关闭 using 块中上下文的延迟加载
context.Configuration.LazyLoadingEnabled = false;
。我希望完全实现对象可以防止错误,但这没有任何区别。 - 添加
AsNoTracking()
到查询。这是最后一次尝试,看看我是否可以完全实现对象并防止并发问题。可悲的是它没有用。
解决方案
你的问题在GetTowns
. 这是该代码正在执行的操作:
- 创建一个
query
. _townRepository
针对get异步执行该查询townList
。- 启动几个任务,其中几个可能正在调用
CheckTownSpecialProperty
. - 使用 . 异步等待所有这些任务完成
Task.WhenAll
。
所以CheckTownSpecialProperty
可能会被同时调用,并且所有那些并发执行都使用相同的_townRepository
. 这是不允许的。
要解决此问题,请执行以下任一操作:
- 使您的原始文件
query
更复杂(例如,进行连接),这样就不需要进行第二次查找了。 - 在 中新建一个存储库
CheckTownSpecialProperty
,以便每个并发执行都有自己的存储库。 - 更改逻辑,以便
CheckTownSpecialProperty
一次运行一个辅助查询。它们仍然可以是异步的,但是是串行的。 - 将辅助查询组合成一个辅助查询。
推荐阅读
- python - 无法将 CustomModel 分配给 QQmlListModel
- javascript - 检查 setInterval 内的条件时,clearInterval 不起作用
- flutter - 更改按钮 onPressed 的背景颜色和文本颜色,并在未按下时设置回默认值 Flutter
- javascript - Javascript json读取数据
- azure-devops - 从 Power BI 中的 Azure DevOps 创建跨组织的 Sprint Burndown 图表
- angular - 如何在 fullCalendar 中显示新事件
- python - assert_frame_equal 导出数据帧和失败断言的差异
- invoke - 如何覆盖 pyinvoke 先前定义的规则,但“super()”调用较旧的实现?
- python-3.x - Airflow:从 Kafka 获取消息
- scala - Akka Streams 按源单累积