Обновление привязки WPF в фоновом потоке

У меня есть элемент управления, который имеет свои данные, привязанные к стандарту ObservableCollection, и у меня есть фоновая задача, которая вызывает службу для получения большего количества данных.

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

Могу ли я обойти это, чтобы мои анимации и прочее продолжали работать в моем диалоговом окне "Подождите"?

Или, по крайней мере, дать "внешнему виду" пользователю, что он не заблокирован?

Ответы

Ответ 1

Если я правильно понимаю, вы уже используете BackgroundWorker для извлечения данных, и простое назначение этих данных ObservableCollection блокирует пользовательский интерфейс.

Один из способов избежать блокировки пользовательского интерфейса - назначить данные ObservableCollection в меньших фрагментах путем очередности нескольких методов диспетчера. Между каждым вызовом метода можно обрабатывать события пользовательского интерфейса.

следующее добавит по одному элементу за раз, что немного экстремально, но это иллюстрирует концепцию.

void UpdateItems()
{
    //retrievedItems is the data you received from the service
    foreach(object item in retrievedItems)
        Dispatcher.BeginInvoke(DispatcherPriority.Background, new ParameterizedThreadStart(AddItem), item);    
}

void AddItem(object item)
{
    observableCollection.Add(item);
}

Ответ 2

ObservableCollection будет вызывать события CollectionChanged, которые заставят интерфейс пересобирать данные, измерять, упорядочивать и перерисовывать. Это может занять много времени, если у вас появилось много обновлений.

Можно заставить пользователя думать, что пользовательский интерфейс жив, разделив задание на небольшие пакеты. Используйте Dispatcher из потока пользовательского интерфейса (любой элемент управления имеет ссылку на него), чтобы запланировать действия по обновлению коллекции с 10-100 элементами (определите число по экспериментам, это просто для поддержки идеи).

Код фона может выглядеть следующим образом:

void WorkInBackground()
{
    var results = new List<object>();

    //get results...

    // feed UI in packages no more than 100 items
    while (results.Count > 0)
    {
        Application.Current.MainWindow.Dispatcher.BeginInvoke(
            new Action<List<object>>(FeedUI),
            DispatcherPriority.Background,
            results.GetRange(0, Math.Min(results.Count, 100)));
        results.RemoveRange(0, Math.Min(results.Count, 100));
    }
}
void FeedUI(List<object> items)
{
    // items.Count must be small enough to keep UI looks alive
    foreach (var item in items)
    {
        MyCollection.Add(item);
    }
}

Ответ 3

используйте BackgroundWorker для выполнения этой задачи. обновить obsrvablecollection в методе DoWork

Ответ 4

У меня есть DLL, которая запускает рабочий поток и отправляет события обратно в приложение - отлично работала на формах окон, переключилась на WPF и все перестало работать. Я бил головой о кирпичную стену в течение 4 часов, пытаясь заставить ее работать. Но решение, с которым я столкнулся, благодаря Microsoft UI Thread Safe marshalling EnableCollectionSynchronization, дает действительно чистую реализацию, чтобы решить эту проблему.

Эта коллекция расширяет ObservableCollection и реализует EnableCollectionSynchronization, позволяющую использовать эти объекты между WPF, а также фоновыми работниками.

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

Параметр контекста - это произвольный объект, который можно использовать для информации, известной при включении синхронизации синхронизации. Контекст может быть null.

ThreadSafeCollection.cs

using System.Collections.ObjectModel;
using System.Windows.Data;

namespace NSYourApplication
{
    /// <summary>
    /// This ObservableCollection is thread safe
    /// You can update it from any thread and the changes will be safely
    /// marshalled to the UI Thread WPF bindings
    /// Thanks Microsoft!
    /// </summary>
    /// <typeparam name="T">Whatever type of collection you want!</typeparam>
    public class ThreadSafeCollection<T> : ObservableCollection<T>
    {
        private static object __threadsafelock = new object();

        public ThreadSafeCollection()
        {
            BindingOperations.EnableCollectionSynchronization(this, __threadsafelock);
        }
    }
}

Пример WindowViewModel WindowViewModel.cs

namespace NSYourApplication
{
    /// <summary>
    /// Example View 
    /// BaseModelView implements "PropertyChanged" to update WPF automagically
    /// </summary>
    class TestViewModel : BaseModelView
    {
        public ThreadSafeCollection<string> StringCollection { get; set; }

        /// <summary>
        /// background thread implemented elsewhere...
        /// but it calls this method eventually ;)
        /// Depending on the complexity you might want to implement
        /// [MethodImpl(MethodImplOptions.Synchronized)]
        /// to Synchronize multiple threads to prevent chase-conditions,deadlocks etc
        /// </summary>
        public void NonUIThreadMethod()
        {
            // No dispatchers or invokes required here!
            StringCollection.Add("Some Text from a background worker");
        }

        /// <summary>
        /// Somewhere in the UIThread code it'll call this method
        /// </summary>
        public void UIThreadMethod()
        {
            StringCollection.Add("This text come from UI Thread");
        }

        /// <summary>
        /// Constructor, creates a thread-safe collection
        /// </summary>
        public TestViewModel()
        {
            StringCollection = new ThreadSafeCollection<string>();
        }
    }
}

Использование в списке в окне xaml/control MainWindow.xaml

    <ListBox x:Name="wpfStringCollection" ItemsSource="{Binding StringCollection,Mode=OneWay}">

    </ListBox>

Ответ 5

Используйте это:


Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(UpdateData), value);

private void UpdateData(int value)
{
  BindingSourceProperty = value;
}