【问题标题】:WPF UI blocking while awaiting DbContext等待 DbContext 时 WPF UI 阻塞
【发布时间】:2015-09-29 03:50:53
【问题描述】:

我正在尝试异步等待,但遇到了不应该发生的 UI 阻塞。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        LoadButton.Click += LoadButton_OnClick;
    }

    private async void LoadButton_OnClick(object sender, RoutedEventArgs e)
    {
        LoadButton.IsEnabled = false;

        // await Task.Delay(2000);

        using(TestContext ctx = new TestContext())
        {
            IList<User> users = await ctx.Users.ToListAsync();
        }

        LoadButton.IsEnabled = true;
    }
}

如果我注释 DbContext 位并取消注释 Task.Delay,它的行为与预期一样 - 不会阻塞 UI。

根据我的理解,ToListAsync() 方法仍然从 UI 线程调用,但不应阻止它。即使该方法受 CPU 限制(可能不是),它也会导致延迟,而不是完整的块。

我的问题:

我理解正确吗?

为什么我的 UI 在等待 ToListAsync() 时会阻塞?


编辑

我尝试在调用此方法之前进行数据库调用以加热所有内容并确保它不会在建立第一个连接时阻塞。此外,我尝试向 DbSet 添加数千个条目并等待 SaveChangesAsync 并且发生了同样的事情 - UI 完全冻结了几秒钟。


编辑 2

我尝试了另一个具有相同代码的示例,它似乎可以正常工作。不同之处在于,在第一个示例中,我使用的是 Code First 和 SQL CE,而在工作示例中,我使用的是 Database First 和 SQL Server。

【问题讨论】:

  • Task.Delay(2000) 当然会阻塞代码2秒。您应该解释为什么要延迟 2 秒。
  • @Hopeless 他正在测试 UI 在等待时不会阻塞。模拟 CPU 密集型任务
  • @alexw 你的意思是他包括那行来测试它吗?
  • @Hopeless 是的,只是为了测试。在 2 秒内,UI 仍然响应。当它访问数据库时,它没有响应
  • 也许是建立与数据库的连接是导致冻结的原因?删除ToListAsync,只剩下一个空的using,看看它是否仍然存在。

标签: c# wpf entity-framework async-await


【解决方案1】:

SQL Server CE 中使用的连接对象不是threadsafe(自然没有实现异步 API)。我相信您在使用 SQL CE 时遇到了这个问题,因为它正在创建 DbContext 的线程上执行,并且在等待时不会将控制权返回给 UI 线程。尝试在 Task 中实例化 DbContext 并等待结果。

LoadButton.IsEnabled = false;

var users = await Task.Run(() => 
{
    using(TestContext ctx = new TestContext())
    {
        return ctx.Users.ToList();
    }
});

LoadButton.IsEnabled = true;

【讨论】:

  • 我相信线程安全与这个例子无关,因为一切都在同一个线程(UI)中运行。我知道如何通过在线程池上排队工作来解决这个问题,但关键是在没有额外线程的情况下实现非阻塞行为。
  • @LukaSverko 是的,这正是您的 UI 线程阻塞的原因。 SQL Server Provider for Entity Framework 6 实现了自然的异步方法,在等待时将控制权返回给 UI 线程,而 SQL Server CE Provider 没有。如果您以这种方式实现它,它将解决 SQL Server CE 的限制,并且您的 UI 线程不会阻塞。
【解决方案2】:

似乎 SQL Server Compact ADO.NET 提供程序不提供异步 API,这就是它阻塞的原因。我找不到这方面的确切来源,但this answer 明确说明了这一点。

【讨论】:

  • 是的,请参阅我的回答,了解如何通过在单独的线程上运行查询来规避此问题。
【解决方案3】:

我相信 ui 被这条线挡住了

using(TestContext ctx = new TestContext())

尝试这样的事情(只是为了检查这是问题所在)

await Task.Run(() => {using(TestContext ctx = new TestContext())
    {
        IList<User> users = ctx.Users.ToList();
    }})

【讨论】:

  • 它没有被这一行阻止 - 我尝试将上下文作为类成员并在执行任何工作之前对其进行实例化并为每个请求重用它。
【解决方案4】:

await 仅适用于使用 async keyword 定义且具有 Task as 返回类型的函数。

在第一种情况下,Task.Delay(2000) 将返回 Timespan,因此 await 没有影响。

虽然在第二种情况下 ctx.Users.ToListAsync() 返回一个Task,然后我们调用await,这导致当前(UI)线程阻塞。

有关详细信息,请参阅 Microsoft 的 link

【讨论】:

  • 我认为等待 ctx.Users.ToListAsync() 不会阻塞 UI 线程。
猜你喜欢
  • 2016-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-06
  • 1970-01-01
  • 2022-01-04
相关资源
最近更新 更多