从另一个线程更新 Datagrid 的 ItemSource

Update the ItemSource of a Datagrid from another thread

我在这里看到过许多此类问题的问答,但其中 none 似乎解决了我的问题。

我有一个页面可以检索并显示来自数据库的数据列表。我的初始代码如下所示

   private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
    {
       //''''''
        _invoices = Invoice.GetAll(); // returns a list of invoices
         InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
        DgInvoices.ItemsSource = InvoiceList.CurrentItems;
      //''''''''' 
    }

在数据列表变大之前,这一切正常。现在这个操作大约需要 6-8 秒。 然后我尝试从另一个线程获取数据并从那里更新 Datagrid ( DGInvoices )。

   private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
    {
       //''''''''

        new Thread(() =>
        {
            _invoices = Invoice.GetAll();
              InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
            DgInvoices.ItemsSource = InvoiceList.CurrentItems;
        }).Start();
    }

抛出这个异常

The Calling thread cannot access this object because a different thread owns it

四处搜索后,我发现 Dispatcher 是解决此问题的方法。但我无法让它工作。

    private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
    {
       //''''''''
        new Thread(() =>
        {
            _invoices = Invoice.GetAll();
              InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
            Dispatcher.Invoke(() =>
            {
                DgInvoices.ItemsSource = InvoiceList.CurrentItems;
            });

        }).Start();
    }

这仍然会抛出上述异常。

你能推荐一种方法来实现它吗?

为什么要在新线程中使用 Dispatcher?

您可以在新线程之外简单地使用 Dipatcher。

像这样:

Dispatcher.Invoke(() =>
        {
            DgInvoices.ItemsSource = InvoiceList.CurrentItems;
        });

所以你可以在主线程上调用而不是在新线程上调用

我个人认为 BackgroundWorker 是最好的选择。 Dispatcher 可能有效,但它在 WPF 中更 "forced" 操作,有时会出现一连串其他问题。使用 BackgroundWorker,您可以在后台完成数据工作,然后在主线程完成后在主线程上完成 UI 工作。

举个例子:

BackgroundWorker bw = new BackgroundWorker();

public MainWindow()
{
    InitializeComponent();

    //Subscribe to the events
    bw.DoWork += Bw_DoWork;
    bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
}

private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
{
     //Start background worker on page load
     bw.RunWorkerAsync(); //This is the DoWork function
}

//Background worker executes on separate thread
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
     //Do long running operations
     _invoices = Invoice.GetAll();           
}

//Fires when the DoWork operation finishes. Executes on the main UI thread
private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
     //Update UI when the worker completes on the main thread
     InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
     DgInvoices.ItemsSource = InvoiceList.CurrentItems;
}

如果您的操作变得很长,您甚至可以利用 BackgrounWorker.ReportProgess 操作并向 UI 提供状态更新。这是一个很好的加载操作工具,您可以使用它来避免锁定 UI.

不要直接在线程内更新 DgInvoices.ItemsSource。 而是将 ItemSource 绑定到 属性 并更新线程中的 属性。

在您上次编辑后,要使​​其正常工作,您必须移动 InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage) 也分配给了 Dispatcher,因为它的子项 CurrentItems 已分配给 DitaGrid 的项源。因此,您不能从主线程 UI 以外的线程修改 InvoiceList

此外,我建议使用 Task 而不是 Thread,因为创建线程的操作成本太高,而且 Task 可能会重用已创建的线程并节省您的时间和 PC 资源。 TaskThread.

的智能包装器
private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
{
    //''''''''
    Task.Run(() =>
    {
        _invoices = Invoice.GetAll();
        Dispatcher.Invoke(() =>
        {
            InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
            DgInvoices.ItemsSource = InvoiceList.CurrentItems;
        });
    });
}

或者如果您的 API 有异步方法来获取数据,您可以使用异步方法。但我不知道是否存在这种可等待的方法。

private async void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
{
    //''''''''
    await _invoices = Invoice.GetAllAsync();
    InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
    DgInvoices.ItemsSource = InvoiceList.CurrentItems;
}