我有一个后台线程,它会生成一系列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
的例子,但我似乎无法想出一个解决这个问题的例子。请指教。
答案 0 :(得分:13)
以下使用调度程序在UI线程上执行Action委托。这使用同步模型,备用Dispatcher.BeginInvoke将异步执行委托。
var backgroundThreadImage = GenerateImage(coordinate);
GeneratedImage.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
<强>更新强>
正如评论中所讨论的那样,由于未在UI线程上创建BitmapImage
,因此上述单独操作无效。如果您无意在创建图像后修改图像,可以使用Freezable.Freeze将其冻结,然后在调度程序委托中分配给GeneratedImage(BitmapImage变为只读,因此由于冻结而导致线程安全) 。另一种选择是将图像加载到后台线程上的MemoryStream中,然后使用该流和BitmapImage的StreamSource属性在调度程序委托中的UI线程上创建BitmapImage。
答案 1 :(得分:8)
你需要做两件事:
您需要从生成器线程访问UI线程的Dispatcher。最灵活的方法是在主线程中捕获值Dispatcher.CurrentDispatcher并将其传递给生成器线程:
public void InitiateGenerateImages(List<Coordinate> coordinates)
{
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
...
如果您知道只在正在运行的应用程序中使用它,并且该应用程序只有一个UI线程,您只需调用Application.Current.Dispatcher
即可获得当前的调度程序。缺点是:
在生成器线程中,在生成图像后添加对Freeze的调用,然后使用Dispatcher转换到UI线程来设置图像:
var backgroundThreadImage = GenerateImage(coordinate);
backgroundThreadImage.Freeze();
dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
<强>澄清强>
在上面的代码中,从UI线程而不是从生成器线程访问Dispatcher.CurrentDispatcher是至关重要的。每个线程都有自己的Dispatcher。如果从生成器线程调用Dispatcher.CurrentDispatcher,您将获得其Dispatcher而不是您想要的Dispatcher。
换句话说,你必须这样做:
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
而不是这个:
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, Dispatcher.CurrentDispatcher));
答案 2 :(得分:0)
在后台线程中使用Streams。
例如,在后台线程中:
var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative);
StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri);
var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream;
SendStreamToDispatcher(_defaultArtPlaceholderStream);
在UI线程中:
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!
}));
}