У меня есть фоновый поток, который генерирует серию 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
, но я, может казаться, не придумываю пример, который решает эту проблему. Советуйте.
В следующем примере диспетчер используется для выполнения делегата действия в потоке пользовательского интерфейса. При этом используется синхронная модель, альтернативный Dispatcher.BeginInvoke будет выполнять делегат асинхронно.
var backgroundThreadImage = GenerateImage(coordinate);
GeneratedImage.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
ОБНОВЛЕНИЕ
Как обсуждалось в комментариях, одно только вышеупомянутое не будет работать, поскольку BitmapImage
не создается в потоке пользовательского интерфейса. Если у вас нет намерения изменять изображение после того, как вы его создали, вы можете заморозить его с помощью Freezable.Freeze , а затем назначить GeneratedImage в делегате диспетчера (BitmapImage становится доступным только для чтения и, следовательно, в результате Замораживания). Другой вариант - загрузить изображение в MemoryStream в фоновом потоке, а затем создать BitmapImage в потоке пользовательского интерфейса в делегате диспетчера с этим потоком и свойством StreamSource BitmapImage.
Вам нужно сделать две вещи:
Вам потребуется доступ к Dispatcher потока пользовательского интерфейса из потока генератора. Самый гибкий способ сделать это - захватить значение Dispatcher.CurrentDispatcher в основном потоке и передать его в поток генератора:
public void InitiateGenerateImages(List<Coordinate> coordinates)
{
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
...
Если вы знаете, что будете использовать это только в работающем приложении и что приложение будет иметь только один поток пользовательского интерфейса, вы можете просто вызвать Application.Current.Dispatcher
, чтобы получить текущий диспетчер. Недостатки:
В потоке генератора добавьте вызов 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));