取消长时间运行的Entity Framework 6异步请求

6
我在 WPF 应用程序中使用 Entity Framework 6 (DbContext),希望找到一种适当的方法来取消 async 数据加载方法 (ToListAsyncFirstOrDefaultAsync),以便我可以立即开始另一个请求。我试图坚持每个表单 (我的情况下是选项卡) 单一上下文的标准,至今为止,我通过确保在请求期间禁用 UI 来处理这些调用的不安全性,这样用户就无法在一个请求正在进行时启动任何新请求。然而,我现在遇到了一种无法这样做的用例。在某些长时间运行的请求期间需要保持 UI 的响应能力,为此,我需要一种方法来取消当前请求并立即开始另一个请求。
我尝试利用添加到异步方法的 CancellationToken 支持,但我发现当我取消请求时,它实际上并没有取消任何东西。它将正确地抛出 OperationCanceledException,但请求仍在进行中,当我在那之后尝试进行另一个请求时,我仍然会得到 NotSupportedException (A second operation started on this context...)
我正在使用更改跟踪,因此更改应用程序以为每个请求使用新的上下文是不现实的。
此外,我暂时解决了这个问题,方法是在这个特定的视图模型在一个请求已经在进行时发出请求时处理当前上下文并创建一个新上下文。从技术上讲,这解决了我的问题,但我想知道是否有一种方法可以在保持相同上下文的同时完成此操作。
那么,是否有人有相关经验呢?我发现很难相信我是第一个遇到这个问题的人,但我在这里找到的所有其他类似问题的答案都建议我使用 CancellationToken(它无法正常工作)或者有点过时,不适用于 Async 方法。
编辑 1:
由于没有人回答这个问题,我开始真正想知道我的选择是什么。一些背景信息。我正在将 Silverlight 应用程序转换为 WPF。Silverlight 应用程序正在使用带有 EF 4.1 的 WCF RIA 服务,但对于 WPF 应用程序,我们决定只使用 EF6.1。
使用 Silverlight 和 WCF,您可以同时无限制地进行异步调用,并且我们实际上为整个应用程序使用单个上下文 (不好,我知道,但简单而且我们从未遇到任何问题)。我们直接绑定到实体,并使用更改跟踪来保存用户所做的更改。
在 WPF 中,使用 EF 6.1 和 Async 方法,在有时需要取消应用程序正在进行的操作并执行用户所需操作的真实世界中,是否根本没有办法做到,而不会崩溃呢?

你能否分享一下你如何使用取消令牌的代码(在哪里传递它给查询以及如何触发它)? - Jason W
我已经在这个时候删除了它,但是我所拥有的是View Model实例化了一个新的CancellationTokenSource。然后,它会将CancellationToken传递给ToListAsync、FirstOrDefaultAsync和SaveChangesAsync方法。当需要时,VM会调用CancallationTokenSource上的Cancel方法,并且发出请求的VM中的代码被try catch包围,以处理OperationCanceledException。 - Teddy
除了使用SQL连接或事务级别的超时之外,没有办法取消已发送到数据库的实际查询。您还可以确保调用“ThrowIfCancellationRequested”,以确保在SaveChangesAsync或ToListAsync之后抛出OperationCancelledException。我在“no”中加上了引号,因为我认为您可以在数据库级别上杀死正在运行的进程的spid,这将强制回滚事务,但它仍将运行直到回滚完成。 - Jason W
异常明显是由EF异步方法抛出的。 我添加了一些日志,大概会得到以下内容: 17:50:57:028 [DEBUG] GetListAsync 请求 17:50:58:032 [DEBUG] 取消请求 17:50:58:160 [DEBUG] 捕获 OpeationCanceledException 17:50:59:028 [DEBUG] GetListAsync 响应:.. - Teddy
1个回答

0

现在先发布我的解决方案。虽然我不是很喜欢,但这是我能够让它正常工作而不需要完全重写此应用程序的唯一方法。

我现在在访问或保存已跟踪实体的存储库方法中使用 AsyncExAsyncLock 类。每个 DbContext 对象都使用自己的锁,因此我不会阻塞其他上下文或未跟踪实体的调用。

例如,在我的存储库中,我的 GetListAsync 方法:

public async virtual Task<IList<T>> GetListAsync(IDbContext context,
                                                 Expression<Func<T, bool>> where,
                                                 IOrderByClause<T>[] orderBy = null,
                                                 params Expression<Func<T, object>>[] navigationProperties)
{
    using (await context.ContextLock.LockAsync())
    {
        IList<T> list = await GetListQuery(context, where, orderBy, navigationProperties).ToListAsync();
        return list;
    }
}

GetListQuery使用导航属性以及where和order by子句创建查询。

我可能也会添加一个超时参数,使用CancellationToken。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接