ReactiveUI: задача Scheudle в потоке пользовательского интерфейса из фонового потока

#c# #system.reactive #reactiveui

#c# #system.reactive #reactiveui

Вопрос:

Мне нужно обработать большой набор данных в фоновом потоке, а затем отобразить эти данные в элементах управления DataGrid в интерфейсе WPF. Данные поступают с внешнего промышленного устройства, для которого мне предоставлена сторонняя библиотека. Итак, я получаю данные в делегате из этой библиотеки.

Моя проблема в том, что я, похоже, не могу запланировать операцию в потоке MainUI из фонового потока, и я не уверен, как решить эту проблему.

Модель представления данных:

     public class DataViewModel : ReactiveObject
    {
        public SourceList<SummaryData> SummaryData = new SourceList<SummaryData>();
        public ReadOnlyObservableCollection<SummaryData> SummaryDataView;


        public SourceList<EnergyTable> SampleData = new SourceList<EnergyTable>();
        public ReadOnlyObservableCollection<EnergyTable> SampleDataView;


        public DataViewModel()
        {
            SummaryData.Connect()
                .Bind(out SummaryDataView)
                .Subscribe();

            SampleData.Connect()
                .Bind(out SampleDataView)
                .Subscribe();

        }

    }
  

Делегат сторонней библиотеки отправляет данные.

         protected void OnSqlCommandCode(Tag tag)
        {
            // Process the 

            Observable.Start(
                 () => HandleData(),
                 RxApp.TaskpoolScheduler)
                 .ObserveOn(RxApp.MainThreadScheduler);
        }



  

Данные обрабатываются…

     public void HandleData()
    {

        ImpactorResultData resu<

        ... Processing ...

        // NOW I TRY TO UPDATE THE COLLECTIONS FROM THE MAIN UI THREAD
         Observable.Start(
            () => UpdateUiThreadCollections(result),
            RxApp.MainThreadScheduler
         );       
    }
  

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

         /// <summary>
        /// Collections tied to data linked to a UI control need to be called from the 
        /// UI thread.
        /// 
        /// The other option is to remake the ObservableRangeCollection every time
        /// from the list. 
        /// </summary>
        protected static void UpdateUiThreadCollections(
            ImpactorResultData result
        )
        {
            dataViewModel.SummaryData.Add(result.SummaryData);

            dataViewModel.SampleData.Edit(innerList =>
            {
               innerList.Clear();
               innerList.AddRange(result.Samples);
           });

        }
  

По какой-то причине оба:

          Observable.Start(
            () => UpdateUiThreadCollections(result),
            RxApp.MainThreadScheduler
         );     

          RxApp.MainThreadScheduler.Schedule(() =>
          {
              UpdateUiThreadCollections(result);
          });
  

все еще планируйте операцию в фоновом потоке. Должно быть что-то, чего я не понимаю в ReactiveUI или реактивных расширениях.

Заранее благодарю вас за любую информацию.

Комментарии:

1. С вызовами, подобными Observable.Start(() => UpdateUiThreadCollections(result), RxApp.MainThreadScheduler) наблюдаемому, фактически не запускается, пока вы не вызовете .Subscribe . Как вы подписываетесь на эти наблюдаемые?

2. Observable.Start( () => UpdateUiThreadCollections(result), RxApp.MainThreadScheduler); функция вызывается без сбоев, нет необходимости подписываться.

Ответ №1:

Вы должны иметь возможность обновлять свой SourceList из фонового потока. Вам не нужна вся эта сложная логика. Вместо этого используйте System.Reactive.Linq и ObserveOn() расширение непосредственно перед выполнением привязки к свойству, которое фактически использует ваш пользовательский интерфейс.

В вашей viewmodel:

             SummaryData.Connect()
                .ObserveOn(RxApp.MainThreadScheduler)
                .Bind(out SummaryDataView)
                .Subscribe();

            SampleData.Connect()
                .ObserveOn(RxApp.MainThreadScheduler)
                .Bind(out SampleDataView)
                .Subscribe();

  

О, и я не пользователь Wpf, но я помню, что были некоторые дополнительные пакеты ReactiveUI специально для Wpf.

Комментарии:

1. Я не уверен, правильно ли это. Подробнее см. Мой комментарий к вопросу.

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

3. Просто чтобы убедиться, что вы установили ReactiveUI. Пакет Wpf в дополнение к обычному пакету ReactiveUI? Потому что пакет Wpf переопределяет свойство RxApp.MainThreadScheduler на что-то отличное от значения по умолчанию.

4. Это правильный ответ и стандартный подход для сортировки обновлений в потоке пользовательского интерфейса.

5. Lee — Существует основной проект, который представляет собой проект .NET Framework / WPF. Однако модель данных, запускающая событие, находится в другом проекте, который является .NET Standard 2.0. Означает ли это, что я создал два MainThreadSchedulers?