Как передать BitmapImage из фонового потока в поток пользовательского интерфейса в WPF? - PullRequest
22 голосов
/ 14 июня 2010

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

Это проект 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 потоку пользовательского интерфейса, где он станет uiThreadImage, затем установить GeneratedImage = uiThreadImage, чтобы представление могло обновляться. Я рассмотрел несколько примеров, связанных с WPF Dispatcher, но, похоже, не могу придумать пример, который решает эту проблему. Пожалуйста, сообщите.

Ответы [ 3 ]

13 голосов
/ 14 июня 2010

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

var backgroundThreadImage = GenerateImage(coordinate);

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

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

8 голосов
/ 14 июня 2010

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

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

Вам потребуется доступ к диспетчеру потока пользовательского интерфейса из потока генератора. Самый гибкий способ сделать это - захватить значение 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));
0 голосов
/ 23 сентября 2011

В фоновом потоке работают с потоками.

Например, в фоновом потоке:

var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative);

StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri);

var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream;

SendStreamToDispatcher(_defaultArtPlaceholderStream);

В потоке пользовательского интерфейса:

void SendStreamToDispatcher(Stream imgStream)
{
     dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
     {
        var imageToDisplay = new BitmapImage();
        imageToDisplay.SetSource(imgStream);

        //Use you bitmap image obtained from a background thread as you wish!
     })); 
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...