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

时间:2010-06-14 04:00:49

标签: c# wpf multithreading mvvm

我有一个后台线程,它会生成一系列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的例子,但我似乎无法想出一个解决这个问题的例子。请指教。

3 个答案:

答案 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)

你需要做两件事:

  1. 冻结您的BitmapImage,以便将其移动到UI线程,然后
  2. 使用Dispatcher转换到UI线程以设置GeneratedImage
  3. 您需要从生成器线程访问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即可获得当前的调度程序。缺点是:

    1. 您无法独立于构造的Application对象使用视图模型。
    2. 您的应用程序中只能有一个UI线程。
    3. 在生成器线程中,在生成图像后添加对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!
     })); 
}