WPF update binding in a background thread

后端 未结 5 1279
情话喂你
情话喂你 2020-12-25 07:52

I have a control that has its data bound to a standard ObservableCollection, and I have a background task that calls a service to get more data.

I want

相关标签:
5条回答
  • 2020-12-25 08:22

    Use this:

    
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(UpdateData), value);
    
    private void UpdateData(int value)
    {
      BindingSourceProperty = value;
    }
    
    
    
    0 讨论(0)
  • 2020-12-25 08:25

    ObservableCollection will raise CollectionChanged events that will force UI to rebind data, measure, arrange and redraw. This might take a lot of time if you have many updates coming.

    It is possible to make user think that UI is alive by splitting the job in small packages. Use Dispatcher from UI thread (any control has reference to it) to schedule collection update actions with 10-100 items (determine number by experiment, these just to support the idea).

    Your background code might looks like this:

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-25 08:33

    use BackgroundWorker to accomplish this task. update the obsrvablecollection in the DoWork method

    0 讨论(0)
  • 2020-12-25 08:35

    If i understand correctly, you already use a BackgroundWorker to retrieve the data, and that simply assigning this data to the ObservableCollection is locking up the UI.

    One way to avoid locking up the UI is to assign the data to the ObservableCollection in smaller chunks by queuing multiple dispatcher methods. Between each method call, UI events can be handled.

    the following would add one item on at a time, that's a bit extreme, but it illustrates the concept.

    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);
    }
    
    0 讨论(0)
  • 2020-12-25 08:45

    I have a DLL which runs a worker thread and sends events back to the application - worked perfectly on windows forms, switched to WPF and everything stopped working. I've been smashing my head against a brick wall for 4 hours trying to get this to work. But the solution I ended up with, thanks to Microsoft's UI Thread Safe marshalling EnableCollectionSynchronization, gives a really clean implementation to solve this.

    This Collection extends ObservableCollection and implements EnableCollectionSynchronization making these objects usable between WPF and also background workers.

    Edit: Microsoft's docs say the following, so I'm going to assume that the object context sharing doesn't matter.

    The context parameter is an arbitrary object that you can use to information known when you enable collection synchronization. Context can be 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);
            }
        }
    }
    

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

    Usage in a listbox in a xaml window/control MainWindow.xaml

        <ListBox x:Name="wpfStringCollection" ItemsSource="{Binding StringCollection,Mode=OneWay}">
    
        </ListBox>
    
    0 讨论(0)
提交回复
热议问题