如何将BitmapImage从后台线程传递到WPF中的UI线程?

我有一个后台线程,生成一系列BitmapImage对象。 每次后台线程完成生成位图时,我想向用户显示此位图。 问题是弄清楚如何将BitmapImage从后台线程传递到UI线程。

这是一个MVVM项目,所以我的视图有一个Image元素:

  

我的视图模型有一个属性GeneratedImage

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

我的视图模型还有创建后台线程的代码:

 public void InitiateGenerateImages(List coordinates) { ThreadStart generatorThreadStarter = delegate { GenerateImages(coordinates); }; var generatorThread = new Thread(generatorThreadStarter); generatorThread.ApartmentState = ApartmentState.STA; generatorThread.IsBackground = true; generatorThread.Start(); } private void GenerateImages(List 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例子,但我似乎无法想出一个解决这个问题的例子。 请指教。

以下使用调度程序在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. 冻结你的BitmapImage,然后将其移动到UI线程
  2. 使用Dispatcher转换到UI线程以设置GeneratedImage

您需要从生成器线程访问UI线程的Dispatcher。 最灵活的方法是在主线程中捕获值Dispatcher.CurrentDispatcher并将其传递给生成器线程:

 public void InitiateGenerateImages(List coordinates) { var dispatcher = Dispatcher.CurrentDispatcher; var generatorThreadStarter = new ThreadStart(() => GenerateImages(coordinates, dispatcher)); ... 

如果您知道只在正在运行的应用程序中使用它,并且应用程序只有一个UI线程,您只需调用Application.Current.Dispatcher即可获取当前的调度程序。 缺点是:

  1. 您无法独立于构造的Application对象使用视图模型。
  2. 您的应用程序中只能有一个UI线程。

在生成器线程中,在生成图像后添加对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)); 

在后台线程中使用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! })); }