dev*_*xer 22 c# wpf multithreading mvvm
我有一个生成一系列BitmapImage对象的后台线程.每次后台线程完成生成位图时,我想向用户显示此位图.问题是弄清楚如何BitmapImage将后台线程传递给UI线程.
这是一个MVVM项目,所以我的视图有一个Image元素:
<Image Source="{Binding GeneratedImage}" />
Run Code Online (Sandbox Code Playgroud)
我的视图模型有一个属性GeneratedImage:
private BitmapImage _generatedImage;
public BitmapImage GeneratedImage
{
get { return _generatedImage; }
set
{
if (value == _generatedImage) return;
_generatedImage= value;
RaisePropertyChanged("GeneratedImage");
}
}
Run Code Online (Sandbox Code Playgroud)
我的视图模型也有创建后台线程的代码:
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?
}
}
Run Code Online (Sandbox Code Playgroud)
我想以某种方式传递backgroundThreadImage到UI线程,它将成为uiThreadImage,然后设置,GeneratedImage = uiThreadImage以便视图可以更新.我已经看过一些处理WPF的例子Dispatcher,但我似乎无法想出一个解决这个问题的例子.请指教.
Sim*_*Fox 13
以下使用调度程序在UI线程上执行Action委托.这使用同步模型,备用Dispatcher.BeginInvoke将异步执行委托.
var backgroundThreadImage = GenerateImage(coordinate);
GeneratedImage.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
Run Code Online (Sandbox Code Playgroud)
更新
正如评论中所讨论的,仅上述内容不起作用,因为BitmapImage未在UI线程上创建.如果您无意在创建图像后修改图像,则可以使用Freezable.Freeze将其冻结,然后在调度程序委托中分配给GeneratedImage(BitmapImage变为只读,因此冻结的结果为线程安全).另一种选择是将图像加载到后台线程上的MemoryStream中,然后使用该流和BitmapImage的StreamSource属性在调度程序委托中的UI线程上创建BitmapImage.
你需要做两件事:
您需要从生成器线程访问UI线程的Dispatcher.最灵活的方式做,这是在主线程捕捉价值Dispatcher.CurrentDispatcher并将其传递到发电机螺纹:
public void InitiateGenerateImages(List<Coordinate> coordinates)
{
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
...
Run Code Online (Sandbox Code Playgroud)
如果你知道你将只能使用一个应用程序运行在应用程序中,这将只有一个UI线程,你可以打电话Application.Current.Dispatcher来获得当前调度.缺点是:
在发电机螺纹,添加一个呼叫到冻结生成图像后,再使用分派器转变到UI线程来设置图像:
var backgroundThreadImage = GenerateImage(coordinate);
backgroundThreadImage.Freeze();
dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
Run Code Online (Sandbox Code Playgroud)
澄清
在上面的代码中,从UI线程而不是从生成器线程访问Dispatcher.CurrentDispatcher是至关重要的.每个线程都有自己的Dispatcher.如果从生成器线程调用Dispatcher.CurrentDispatcher,您将获得其Dispatcher而不是您想要的Dispatcher.
换句话说,你必须这样做:
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
Run Code Online (Sandbox Code Playgroud)
而不是这个:
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, Dispatcher.CurrentDispatcher));
Run Code Online (Sandbox Code Playgroud)