Связывание обновления WPF в фоновом потоке - PullRequest
17 голосов
/ 24 марта 2010

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

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

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

Или, по крайней мере, создать у пользователя «вид», что он не заблокирован?

Ответы [ 5 ]

18 голосов
/ 24 марта 2010

Если я правильно понимаю, вы уже используете 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);
}
10 голосов
/ 24 марта 2010

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);
    }
}
1 голос
/ 22 августа 2017

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

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

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

Параметр context - это произвольный объект, который вы можете использовать для информации, известной при включении синхронизации коллекции. Контекст может быть 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>
0 голосов
/ 24 марта 2010

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


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

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


0 голосов
/ 24 марта 2010

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

...