2014-02-04 3 views
3

Я работаю над приложением WinRT/Windows Store, написанным на C#, который использует MVVM Light. Я пытаюсь изменить метод OnPropertyChanged() MVVM Light, чтобы уведомления о событиях всегда возникали в потоке пользовательского интерфейса, в противном случае элементы интерфейса, привязанные к моим свойствам модели просмотра, будут пропускать уведомления о событиях, поскольку они должны возникать в потоке пользовательского интерфейса. В настоящее время я пробую этот код:Получение интерфейса для маршалинга для другого потока Исключение с вызовом диспетчера потоков пользовательского интерфейса?

/// <summary> 
/// Event handler for the PropertyChanged event. 
/// </summary> 
/// <param name="propertyName">The name of the property that has changed.</param> 
protected void OnPropertyChanged(string propertyName = null) 
{ 
    var eventHandler = PropertyChanged; 
    if (eventHandler != null) 
    { 
     // Make sure the event notification occurs on the UI thread or the INotifyPropertyChanged 
     // notification will not be seen by the consumer of this event or worse, 
     // a "wrong thread" COM Exception will be raised if we are not on the UI thread. 
     var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher; 

     dispatcher.RunAsync(dispatcher.CurrentPriority, 
      () => 
      { 
       eventHandler(this, new PropertyChangedEventArgs(propertyName)); 
      }); 
    } 
} 

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

Первый шанс исключение типа «System.Exception» произошло в Common_WinStore.DLL

WinRT информация: приложение под названием интерфейс, который был ранжированы для другого потока.

Это сбивает с толку, так как точка CoreWindow.Dispatcher является запланировать выполнение кода из потока без пользовательского интерфейса на потоке пользовательского интерфейса. Я прочитал много сообщений об ошибках маршалинга на SO, но ни один из них не имеет дело с получением исключений маршалинга при попытке вызвать RunAsync() на диспетчере сам. Вместо этого они используют код, который я использую, чтобы вылечить возникновение этого Исключения.

Почему я получаю это исключение с Dispatcher.RunAsync() и как его исправить?

КОНТЕКСТ ПРИМЕЧАНИЕ. Я попал в эту ситуацию из-за ситуации взаимоблокировки с некоторым кодом, инициированным элементами интерфейса, которые привязаны к свойствам WriteableBitmap в моей модели представления. ждут звонки зашли бы в тупик, когда ожидаемая задача async попыталась продолжить исходный поток, который был потоком пользовательского интерфейса, и этот поток ожидал завершения вызова. Чтобы решить эту проблему, я добавил ConfigureAwait (false) в wait оператор, чтобы освободить вызов из контекста потока пользовательского интерфейса. Это привело к ошибке Исключение COM, когда MVNM Light OnNotifyPropertyChanged() попытался обновить свойство. Вот почему я пытаюсь вернуть этот код обратно в поток пользовательского интерфейса. Я хочу сделать это в любом случае, потому что, если мне это удастся, у меня нет беспокойства о том, что уведомления о событиях происходят должным образом, независимо от контекста вызова.

ОБНОВЛЕНИЕ: Я добавил AsyncEx библиотеку, предложенную Стивеном Клири. Пока это работает. Сначала у меня была ловушка, как вы можете видеть в ответе на свой ответ. Объекты, которые у меня есть, содержат поле байтов JPEG, которое десериализуется в SQLite-движке. Это означало, что мой конструктор без параметров и поэтому инициирует метод async для преобразования байтов JPEG в объект с возможностью связывания WriteableBitmap. Это произошло потому, что метод асинхронного преобразования начал работать до того, как необходимо было десериализовать байты для преобразования.

Я решил это с помощью объекта AsyncManualResetEvent, который вначале находится в неустановленном состоянии. Свойство set для свойства JPEG bytes задает событие. Метод async-преобразования ожидает этого объекта события и поэтому освобождается для выполнения преобразования сразу после того, как доступны байты.Я отправляю отрывки кода ниже, чтобы увидеть, если Стивен и другие из вас увидеть любые потенциальные проблемы с реализацией или если есть более простой способ для достижения этой цели:

// Create an Async manual reset event for the JPEG conversion method in the unset state. 
    private AsyncManualResetEvent _asyncManResetEvent = new AsyncManualResetEvent(false); 


    /// <summary> 
    /// The jpeg bytes for the JPEG image for the videomark. 
    /// </summary> 
    private byte[] _thumbnailJpegBytes; 

    public byte[] ThumbnailJpegBytes 
    { 
     get 
     { 
      return this._thumbnailJpegBytes; 
     } 

     set 
     { 
      SetProperty(ref this._thumbnailJpegBytes, value, "ThumbnailJpegBytes"); 

      // Release the lock so that the async call waiting to convert the JPEG bytes to 
      // a WriteableBitmap can finish up. 
      _asyncManResetEvent.Set(); 
     } 
    } 

// The WriteableBitmap property. 
[Ignore] 
public INotifyTaskCompletion<WriteableBitmap> ThumbnailAsync 
{ 
    get; 

    private set; 
} 

    // The async method passed to the NotifyTaskCompletion constructor. 
async private Task<WriteableBitmap> ConvertJpegBytesToThumbnail() 
{ 
    // Wait for data to be available. 
    await this._asyncManResetEvent.WaitAsync(); 

    return await Misc.JpegBytesToBitmap(ThumbnailJpegBytes);  
} 
+0

Это произошло неправильно, когда вы создали непереходный объект в неправильном потоке. Windows дала ему безопасный дом, * другой * поток, который может гарантировать, что он используется поточно-безопасным способом. Этот объект подписал событие PropertyChanged. Он получил обратный вызов в потоке пользовательского интерфейса, но это не тот поток, на котором он действительно доволен. Он хочет получить обратный вызов в своей собственной ветке. Поиск этого объекта и его создание в основном потоке пользовательского интерфейса - это решение, над которым вам нужно работать. Решает эту проблему взаимоблокировки. –

+0

HansPassant Потребитель - это элемент пользовательского интерфейса, доступ к которому осуществляется через привязку данных. Я не могу изменить поток, на котором работает объект. –

ответ

4

На мой взгляд, это неправильное решение проблемы , Компоненты пользовательского интерфейса (очевидно) имеют UI-сродство. Я бы сказал, что все данные, привязанные к этим компонентам пользовательского интерфейса, также имеют UI-сродство.

Итак, основная проблема заключается в том, что вы блокируете свойство, связанное с данными. Этого следует избегать в любом случае, особенно в приложениях Windows Store. У меня есть сообщение в блоге, в котором обсуждается использование async properties (the last section covers data-binding).

Таким образом, вы должны использовать такой тип, как NotifyTaskCompletion, из моей библиотеки AsyncEx, чтобы асинхронно инициализировать значение свойства. Это решение обеспечивает два преимущества: ваше приложение реагирует (не блокирует), а код, обновляющий свойство, соответствующим образом привязывается к потоку пользовательского интерфейса, прежде чем поднимать событие PropertyChanged.

+0

Спасибо. Ваши статьи - некоторые из тех, которые я читал, и у меня есть закладки AsynEx. Я попробую и отправлю отчет. –

+0

У меня проблема с реализацией. Я увольняю вызов NotifyTaskCompletion.Create() из конструктора без параметров, когда объекты десериализуются из базы данных SQLite. Это проблема, потому что данные, которые мне нужны для асинхронного вызова, еще нет в объекте, поэтому я получаю исключение NullReferenceException. Можете ли вы подумать об изящном способе задержки асинхронного вызова до тех пор, пока объект не будет полностью десериализован движком SQLite? Я надеюсь избежать цикла TaskEx.Delay(), который проверяет наличие байтов перед продолжением. –

+1

@RobertOschler: Первый вариант, который приходит на ум, состоит в том, чтобы отделить данные db от вашего ViewModel. Я сам делаю очень простые «модельные» слои, но я не зашел так далеко, чтобы десериализовать ViewModels из базы данных ... –

1

Я воспроизвел эту проблему и нашел фактическую причину ... она находится в диспетчере. Текущая цена.

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

Замените это на CoreDispatcherPriority.Normal, который является постоянным, и он будет работать отлично; нет необходимости в каких-либо сторонних библиотеках или что-то интересное.

+0

Спасибо. Знаете ли вы, что найденная вами проблема относится только к приложениям для хранения WinRT/Windows, или это также будет иметь отношение к настольным приложениям Windows 8.1+ или универсальным приложениям Windows 10? –

+0

Привет и извините за поздний ответ - я не проверял этот сайт какое-то время. Я воспроизвел это в приложении UWP в Windows 10. –

Смежные вопросы