我的WPF应用程序由一个TabControl main 组成,它呈现多个插件,通过MEF加载。从技术上讲,插件集合的ICollectionView绑定到主TabControl的ItemsSource-property。IsSynchronizedWithCurrentItem-property还被设置为True,以跟踪当前选定的选项卡项。
要初始化插件CurrentChanged**-event,我订阅** ICollectionView的,通过延迟加载初始化所选的插件。由于许多初始化任务都是异步,所以我也将事件处理程序声明为异步。
private ICollectionView pluginsCv;
public ICollectionView PluginsCv
{
get
{
if (pluginsCv == null)
{
pluginsCv = CollectionViewSource.GetDefaultView(Plugins);
pluginsCv.CurrentChanged += async (sender, args) =>
{
await LoadCurrentSection();
};
}
return pluginsCv;
}
}LoadCurrentSection**-method:**的任务
LoadCurrentSection**-method:**
private async Task LoadCurrentSection()
{
// If no tab is selected, select the first one
if (PluginsCv.CurrentItem == null)
{
// Comment for stackoverflow: Another raise of the CurrentChanged-event does not seem to be the cause. Tried to prevent another execution of LoadCurrentSection via a bool flag, an stayed in this method (no return statement). Same result.
PluginsCv.MoveCurrentToFirst();
return;
}
// Get the ViewModel of the currently selected section/plugin
var pluginViewModel = ((IPlugin)PluginsCv.CurrentItem).PluginElement.DataContext as ISettlementScheduleSection;
// Initialize currently selected section/plugin
if (pluginViewModel != null && !pluginViewModel.DataLoaded)
await pluginViewModel.LoadData();
}插件的LoadData**-method:**
public override async Task LoadData()
{
// ...
Costs = await costsService.GetCosts(SubProjectId, UnitTypeId);
// ...
}每个插件都保存自己的crud操作服务类实例。这些一次性服务类包含数据库上下文的实例。
很小的例子:
public class CostsService : ServiceBase
{
public CostsService()
: base()
{ }
public CostsService(MyDbContext db)
{
this.Context = db ?? throw new ArgumentNullException(nameof(db));
}
public async Task<IEnumerable<CostsDefinition>> GetCosts(Guid subProjectId, Guid unitTypeId)
{
return await Context.CostsDefinitions.Where(cd => cd.SubProjectId == subProjectId
&& cd.UnitTypeId == unitTypeId)
.OrderBy(cd => cd.Year).ThenBy(cd => cd.Month)
.ToListAsync();
}
}db上下文(Context-property)不是静态的,也不是在插件之间共享的。
问题:有时(但不是总是)以这种方式初始化插件会导致System.NotSupportedException: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe. WPF多次引发该事件。对我来说,它在什么情况下被提出是不清楚的。当ICollectionView第一次绑定到TabControl并且第一项被自动选中时?同样,当我加载并插入更多插件到源代码集合时?.
频率的bug:在这种情况下,时间似乎发挥了作用。当不进行调试或在不同的网络条件下运行时,该错误的频率会发生变化。
解决方案尝试:I试图通过bool标志过滤CurrentChanged-handler的额外/不必要的调用,但在所有情况下都没有帮助。我只是把频率降低了一点。我还试图将对PluginsCv的每一次访问都委托给调度程序,但没有帮助。此外,当pluginsCv为null时,我编写了调试信息。我只得到了一次信息,所以CurrentChanged-event也只能订阅一次。
异常详细信息:
例外来源:"EntityFramework“
堆栈跟踪:
at System.Data.Entity.Internal.ThrowingMonitor.EnsureNotEntered()
at System.Data.Entity.Core.Objects.ObjectQuery`1.System.Data.Entity.Infrastructure.IDbAsyncEnumerable<T>.GetAsyncEnumerator()
at System.Data.Entity.Internal.Linq.InternalQuery`1.GetAsyncEnumerator()
at System.Data.Entity.Infrastructure.DbQuery`1.System.Data.Entity.Infrastructure.IDbAsyncEnumerable<TResult>.GetAsyncEnumerator()
at System.Data.Entity.Infrastructure.IDbAsyncEnumerableExtensions.ForEachAsync[T](IDbAsyncEnumerable`1 source, Action`1 action, CancellationToken cancellationToken)
at System.Data.Entity.Infrastructure.IDbAsyncEnumerableExtensions.ToListAsync[T](IDbAsyncEnumerable`1 source, CancellationToken cancellationToken)
at System.Data.Entity.Infrastructure.IDbAsyncEnumerableExtensions.ToListAsync[T](IDbAsyncEnumerable`1 source)
at System.Data.Entity.QueryableExtensions.ToListAsync[TSource](IQueryable`1 source)
at xyz.desk.app.ccm.business.Service.CostsService.<GetCosts>d__7.MoveNext() in D:\Dev\ThatCrazyPluginProject\desk\app\xyz.desk.app.ccm.business\Service\CostsService.cs:line 89
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at xyz.desk.app.ccm.plugin.costplan.constructionprogress.ViewModel.ContructionProgressCostPlanViewModel.<LoadData>d__58.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at xyz.desk.app.ccm.plugin.settlementscheduleshost.ViewModel.SettlementSchedulesHostViewModel.<LoadCurrentSection>d__67.MoveNext() in D:\Dev\ThatCrazyPluginProject\desk\app\xyz.desk.app.ccm.ui\ViewModel\SettlementSchedulesHostViewModel.cs:line 307
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at xyz.desk.app.ccm.plugin.settlementscheduleshost.ViewModel.SettlementSchedulesHostViewModel.<LoadData>d__65.MoveNext() in D:\Dev\ThatCrazyPluginProject\desk\app\xyz.desk.app.ccm.ui\ViewModel\SettlementSchedulesHostViewModel.cs:line 282
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at xyz.desk.app.ccm.ui.ViewModel.ProjectViewModel.<LoadCurrentSection>d__115.MoveNext() in D:\Dev\ThatCrazyPluginProject\desk\app\xyz.desk.app.ccm.ui\ViewModel\ProjectViewModel.cs:line 763
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at xyz.desk.app.ccm.ui.ViewModel.ProjectViewModel.<<get_PluginsCv>b__21_0>d.MoveNext() in D:\Dev\ThatCrazyPluginProject\desk\app\xyz.desk.app.ccm.ui\ViewModel\ProjectViewModel.cs:line 120有什么办法解决这个问题吗?
发布于 2018-04-18 16:10:55
典型的种族状况。
让我们考虑一下这个场景。
Thread1 - Access PluginsCv
PluginsCv为空,所以注册CurrentChanged
Thread2 - Access PluginsCv
PluginsCv为空,所以注册CurrentChanged
以后..。CurrentChanged火灾
Thread1 -等待LoadCurrentSection
Thread2 -等待LoadCurrentSection
Thread2“等等!Thread1也在做LoadCurrentSection,但它还没有完成!错误!错误!”
https://stackoverflow.com/questions/49903996
复制相似问题