Обновите ItemSource Datagrid из другого потока - PullRequest
1 голос
/ 17 апреля 2020

Я видел многие из этих типов вопросов, которые задавали и отвечали здесь, но ни один из них, похоже, не решил мою проблему.

У меня есть страница, которая извлекает и показывает список данных из базы данных. мой исходный код выглядел следующим образом

   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();
    }

, который выдает это исключение

Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им

После поиска я нашел что Диспетчер - это способ go об этом. но я не могу заставить его работать.

    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();
    }

это все еще вызывает вышеприведенное исключение.

Вы можете порекомендовать способ заставить это работать?

Ответы [ 4 ]

1 голос
/ 18 апреля 2020

Я лично думаю, что BackgroundWorker будет лучшим вариантом. Dispatcher может работать, но это более «принудительная» операция в WPF, и иногда она может представлять целый ряд других проблем. С BackgroundWorker вы можете выполнять работу с вашими данными в фоновом режиме, а затем выполнять работу вашего пользовательского интерфейса в основном потоке после его завершения.

Например:

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 и предоставить обновления статуса пользовательскому интерфейсу. Это отличный инструмент для операций загрузки, который можно использовать, чтобы избежать блокировки пользовательского интерфейса.

0 голосов
/ 18 апреля 2020

После вашего последнего редактирования вы должны переместить InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage) на Dispatcher, потому что его дочерний элемент CurrentItems назначен источнику элементов DitaGrid. Таким образом, вы не можете изменять InvoiceList из других потоков, кроме основного пользовательского интерфейса.

Кроме того, я предлагаю использовать Task вместо Thread, поскольку создание потока является слишком дорогой операцией и Task может повторно использовать уже созданные потоки и сэкономить время и ресурсы ПК. Task - это умная оболочка Thread.

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 есть метод asyn c для получения данных, вы можете использовать асинхронный подход. Но я не знаю, существует ли такой ожидаемый метод.

private async void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
{
    //''''''''
    await _invoices = Invoice.GetAllAsync();
    InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
    DgInvoices.ItemsSource = InvoiceList.CurrentItems;
}
0 голосов
/ 18 апреля 2020

Не обновляйте DgInvoices.ItemsSource непосредственно внутри потока. Вместо этого свяжите ItemSource со свойством и обновите свойство в потоке.

0 голосов
/ 17 апреля 2020

Почему вы используете Dispatcher в новом потоке?

Вы можете просто использовать Dipatcher вне нового потока.

Например:

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

Таким образом, вы можете вызывать в основном потоке, а не в новом потоке

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...