【问题标题】:Entity Framework async call blocking UI thread实体框架异步调用阻塞 UI 线程
【发布时间】:2016-07-14 17:00:52
【问题描述】:

我一直在慢慢尝试将我的代码从使用操作委托转换为我的 WPF 应用程序中的新任务。我喜欢一个 await 操作可以在同一个方法中运行的事实,大大减少了我需要的方法数量,增强了可读性,并减少了维护。话虽如此,我在调用 EF6 异步方法时遇到了我的代码问题。它们似乎都在同步运行并阻塞了我的 UI 线程。我在代码中使用了以下技术/框架:

例如,我有一个 LogInViewModel,它带有一个在我的 WPF 应用程序上单击按钮后执行的命令。这是在构造函数中初始化的命令:

LogInCommand = new RelayCommand(() => ExecuteLogInCommand());

这是命令体:

private async void ExecuteLogInCommand()
{
     // Some code to validate user input

     var user = await _userService.LogIn(username, password);

     // Some code to confirm log in
}

用户服务使用使用 MVVM Light 的 SimpleIoC 容器创建的通用存储库对象。 LogIn 方法如下所示:

public async Task<User> LogIn(string username, string password)
{
    User user = await _repository.FindUser(username);

    if (user != null && user.IsActive)
    {
        // Some code to verify passwords
        return user;
    }

    return null;
}

还有我要登录的存储库代码:

public static async Task<User> FindUser(this IRepositoryAsync<User> repository, string username)
{
    return await repository.Queryable().Where(u => u.Username == username).SingleOrDefaultAsync();
}

SingleOrDefaultAsync() 调用是实体框架的异步调用。这段代码阻塞了我的 UI 线程。我已经阅读了 Stephen Cleary 和其他人关于异步等待和正确使用的多篇文章。我一直尝试使用 ConfigureAwait(false),但没有运气。我已经让我的 RelayCommand 调用使​​用了 async await 关键字,但没有成功。我已经分析了代码,返回时间最长的行是 SingleOrDefaultAsync() 行。其他一切几乎都是瞬间发生的。在我的代码中对数据库进行其他异步调用时,我遇到了同样的问题。唯一能立即修复它的是:

User user = await Task.Run(() =>
{
    return _userService.LogIn(Username, p.Password);
});

但我知道这不是必需的,因为我对数据库的调用是 IO 绑定的,而不是 CPU 绑定的。那么,我的应用程序出了什么问题,为什么它会阻塞我的 UI 线程?

【问题讨论】:

  • 存储库类中的.Queryable() 到底是什么?你怎么称呼它? IRepositoryAsync&lt;User&gt; 是否继承自其他接口以及它的具体实现是什么
  • @Tseng Queriable() 只返回底层用户数据库集,您可以对其应用任何实体框架方法调用。 IRepositoryAsync 是 Generic Repository/UnitOfWork 框架提供的接口。它还提供了 EF6 实现。我没有在其中添加任何代码,因为它很广泛。我提供了上面的链接,所以如果你喜欢的话,你可以看看它。谢谢!
  • 您的模式对我来说看起来不错,一些建议:将生成时的语法缩短为new RelayCommand;您不需要 () =&gt; 部分。此外,如果您不修改返回的对象,您可以在存储库代码中删除 asyncawait 关键字。通过这个小的更改,您将保存在这种情况下不必要的任务对象的生成。对于您的async 问题:您可以尝试在IRepositoryAsync&lt;&gt; 上使用Select 方法,而不是Queryable()

标签: wpf mvvm async-await entity-framework-6 mvvm-light


【解决方案1】:

您的 RelayCommand 不是异步的。

LogInCommand = new RelayCommand(() => ExecuteLogInCommand());

因为没有async/await,你的ExecuteLogInCommand会被同步调用。

你得把它改成

LogInCommand = new RelayCommand(async () => await ExecuteLogInCommand());

所以RelayCommand 也被称为异步。

【讨论】:

  • 那只会生成另一个没有实际作用的状态机。
  • @PauloMorgado:但是如果在没有await 的情况下调用ExecuteLogInCommand 或与.ContinueWith() 链接,它将自动同步运行,无论下面的调用是否异步。当使用 async/await 时,你必须一直使用 async
  • @Tseng 我尝试使用 async/await 关键字使命令调用异步。它仍然阻塞。
  • @Tseng,async 修饰符所做的唯一一件事就是生成一个状态机。代码可能只是LogInCommand = new RelayCommand(ExecuteLogInCommand);,它会是一样的。
【解决方案2】:

您的 LogInFindUser(应称为 according to the guidelinesLogInAsyncFindUserAsync)不应与 UI 一起使用,应在所有等待中使用 ConfigureAwait(false)

但是,在调用真正异步的东西之前,所有调用都是同步的。我想应该是SingleOrDefaultAsync

如果将其包装在 Task.Run 中会产生如此大的差异,那么出于某种原因,SingleOrDefaultAsync 必须同步运行。

【讨论】:

  • Paulo Morgado 感谢您的回复。我一直尝试使用 ConfigureAwait(false),但没有成功。 “哪些不应该与 UI 一起使用”是什么意思?谢谢!
  • 任何不应该与 UI 一起工作的方法(意味着独立于同步上下文工作)应该在每个 await 上使用 ConfigureAwait(false)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-07
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多