【问题标题】:How to keep the UI responsive while doing background tasks如何在执行后台任务时保持 UI 响应
【发布时间】:2018-11-09 19:40:51
【问题描述】:

我实现了一个刷新按钮,它读取数据库并检查任何新条目并将它们添加到ObservableCollection。我有一个刷新图标,它根据IsRefreshing 的值旋转。像这样:

new Thread(() =>
        {
            Thread.CurrentThread.IsBackground = true;
            IsRefreshing = true;

            //Reads database
            //ObservableCollection.Add(NewValues)

            IsRefreshing = false;
        }).Start();

但是运行它会给我一个例外:

这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。

我显然是以错误的方式解决这个问题,但不确定在执行后台任务时我应该如何保持 UI 响应?

【问题讨论】:

  • 你不应该访问其他线程在主线程中创建的控件。
  • 不要使用原始线程,使用 async/await 和任务。 ADO.NET 和 EF 已经支持异步执行。在任何情况下,您都不能从另一个线程修改 UI
  • 发布事件处理程序的代码和从数据库中读取的代码。应该删除当前带有Thread 的代码

标签: c# wpf


【解决方案1】:

您不能从后台线程修改 UI。所有受支持的 .NET 版本都允许使用 async/await 在后台执行某些操作并返回 UI 线程。

ADO.NET、Entity Framework、Dapper 和大多数 ORM 支持异步执行。例如,ADO.NET 的 ExecuteReader 具有等效的 ExecuteReaderAsync 方法。 EF 的ToList() 有一个ToListAsync()。 Dapper 的QueryQueryAsync

你应该把你的事件处理程序改成这样:

private async void btn1_Click(object sender, RoutedEventArgs e)
{
    using(var connection = new SqlConnection(_cnString)
    {
        IsRefreshing = true;
        await connection.OpenAsync();
        var results=await connection.QueryAsync<Value>(query);
        //We're now back in the UI thread
        _theCollection.AddRange(results);
        IsRefreshing = false;
    }
}

当调用OpenAsyncQueryAsync 时,.NET 将在后台运行查询并释放当前(UI)线程。当查询完成时await 确保执行将在原始线程上恢复,即UI 线程。

【讨论】:

    【解决方案2】:

    基本上,您必须将此项目添加到集合中,但您必须在 Dispatcher 线程上执行此操作,如下所示:

    Application.Current.Dispatcher.Invoke(() => ObservableCollection.Add(NewValues));
    

    【讨论】:

    • await 可用时不需要。如果您想从另一个任务或线程发布进度更新,请使用 Progress 类。 Async in 4.5: Enabling Progress and Cancellation in Async APIs 中描述了使用它
    • 此外,Application.Current.Dispatcher.Invoke 可以在 UI 线程繁忙时使应用程序死锁
    • @PanagiotisKanavos 我没有从他那里看到太多代码,但你假设了很多。如果他试图添加到绑定到某个 ListView 或类似控件的可观察集合中,那么他就不能这样做,因为 UI 不能从 UI 以外的任何其他线程更新。因此添加到可观察集合需要在 UI 线程上进行。这就是您在 Dispatcher 上调用 Invoke 的原因。
    • 我不认为。在await 之后,您将返回 UI 线程。这意味着您可以修改任何元素。这就是为什么不需要使用 Invoke 的原因。我建议你检查我提供的链接
    • @PanagiotisKanavos 我唯一看到的是他打开了一个完全不同的线程并在这个线程中做他的工作。如果他想从这个线程更新一个 UI 元素,他必须在 UI 线程上调用它。除了调用 Dispatcher.Invoke 之外别无他法
    猜你喜欢
    • 2018-02-05
    • 2018-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多