Как Вы передаете BitmapImage от фонового потока до потока UI в WPF?

У меня есть фоновый поток, который генерирует серию BitmapImage объекты. Каждый раз, когда фоновый поток заканчивает генерировать битовый массив, я хотел бы показать этот битовый массив пользователю. Проблема выясняет, как передать BitmapImage от фонового потока до потока UI.

Это - проект MVVM, таким образом, мое представление имеет Image элемент:

<Image Source="{Binding GeneratedImage}" />

Моя модель представления имеет свойство GeneratedImage:

private BitmapImage _generatedImage;

public BitmapImage GeneratedImage
{
    get { return _generatedImage; }
    set
    {
        if (value == _generatedImage) return;
        _generatedImage= value;
        RaisePropertyChanged("GeneratedImage");
    }
}

Моя модель представления также имеет код, который создает фоновый поток:

public void InitiateGenerateImages(List<Coordinate> coordinates)
{
    ThreadStart generatorThreadStarter = delegate { GenerateImages(coordinates); };
    var generatorThread = new Thread(generatorThreadStarter);
    generatorThread.ApartmentState = ApartmentState.STA;
    generatorThread.IsBackground = true;
    generatorThread.Start();
}

private void GenerateImages(List<Coordinate> coordinates)
{
    foreach (var coordinate in coordinates)
    {
        var backgroundThreadImage = GenerateImage(coordinate);
        // I'm stuck here...how do I pass this to the UI thread?
    }
}

Я хотел бы так или иначе передать backgroundThreadImage к потоку UI, где это станет uiThreadImage, затем набор GeneratedImage = uiThreadImage таким образом, представление может обновить. Я посмотрел на некоторые примеры, имеющие дело с WPF Dispatcher, но я, может казаться, не придумываю пример, который решает эту проблему. Советуйте.

22
задан devuxer 14 June 2010 в 04:00
поделиться

2 ответа

В следующем примере диспетчер используется для выполнения делегата действия в потоке пользовательского интерфейса. При этом используется синхронная модель, альтернативный Dispatcher.BeginInvoke будет выполнять делегат асинхронно.

var backgroundThreadImage = GenerateImage(coordinate);

GeneratedImage.Dispatcher.Invoke(
        DispatcherPriority.Normal,
        new Action(() =>
            {
                GeneratedImage = backgroundThreadImage;
            }));

ОБНОВЛЕНИЕ Как обсуждалось в комментариях, одно только вышеупомянутое не будет работать, поскольку BitmapImage не создается в потоке пользовательского интерфейса. Если у вас нет намерения изменять изображение после того, как вы его создали, вы можете заморозить его с помощью Freezable.Freeze , а затем назначить GeneratedImage в делегате диспетчера (BitmapImage становится доступным только для чтения и, следовательно, в результате Замораживания). Другой вариант - загрузить изображение в MemoryStream в фоновом потоке, а затем создать BitmapImage в потоке пользовательского интерфейса в делегате диспетчера с этим потоком и свойством StreamSource BitmapImage.

13
ответ дан 29 November 2019 в 05:39
поделиться

Вам нужно сделать две вещи:

  1. Заморозить BitmapImage, чтобы его можно было переместить в поток пользовательского интерфейса, затем
  2. Используйте Диспетчер для перехода к потоку пользовательского интерфейса. для установки GeneratedImage

Вам потребуется доступ к Dispatcher потока пользовательского интерфейса из потока генератора. Самый гибкий способ сделать это - захватить значение Dispatcher.CurrentDispatcher в основном потоке и передать его в поток генератора:

public void InitiateGenerateImages(List<Coordinate> coordinates)   
{
  var dispatcher = Dispatcher.CurrentDispatcher;

  var generatorThreadStarter = new ThreadStart(() =>
     GenerateImages(coordinates, dispatcher));

  ...

Если вы знаете, что будете использовать это только в работающем приложении и что приложение будет иметь только один поток пользовательского интерфейса, вы можете просто вызвать Application.Current.Dispatcher , чтобы получить текущий диспетчер. Недостатки:

  1. Вы теряете возможность использовать вашу модель представления независимо от сконструированного объекта Application.
  2. В вашем приложении может быть только один поток пользовательского интерфейса.

В потоке генератора добавьте вызов Freeze после создания изображения, затем используйте Dispatcher для перехода к потоку пользовательского интерфейса для установки изображения:

var backgroundThreadImage = GenerateImage(coordinate);

backgroundThreadImage.Freeze();

dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
   GeneratedImage = backgroundThreadImage;
}));

Пояснение

В приведенном выше коде критически важно, чтобы Dispatcher .CurrentDispatcher можно получить из потока пользовательского интерфейса, а не из потока генератора. У каждого потока есть свой Диспетчер. Если вы вызовете Dispatcher.CurrentDispatcher из потока генератора, вы получите его Dispatcher вместо того, который вам нужен.

Другими словами, вы должны сделать это:

  var dispatcher = Dispatcher.CurrentDispatcher;

  var generatorThreadStarter = new ThreadStart(() =>
     GenerateImages(coordinates, dispatcher));

, а не это:

  var generatorThreadStarter = new ThreadStart(() =>
     GenerateImages(coordinates, Dispatcher.CurrentDispatcher));
8
ответ дан 29 November 2019 в 05:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: