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

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (18)

我有一个后台线程,它生成一系列的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在UI线程上执行Action委托。这使用了一个同步模型将异步执行委托。

var backgroundThreadImage = GenerateImage(coordinate);

GeneratedImage.Dispatcher.Invoke(
        DispatcherPriority.Normal,
        new Action(() =>
            {
                GeneratedImage = backgroundThreadImage;
            }));

正如注释中所讨论的那样,以上内容本身并不能作为BitmapImage不在UI线程上创建。

然后在Dispatcher委托中分配给GeneratedImage(BitmapImage变成只读,因此冻结导致线程安全)。

另一个选项是将图像加载到后台线程上的MemoryStream中,然后在Dispatcher委托中的UI线程上使用该流和BitmapImage的StreamSource属性创建BitmapImage。

用户回答回答于

你需要做两件事:

  1. 冻结BitmapImage,以便将其移动到UI线程,然后
  2. 使用Dispatcher转换到UI线程以设置GeneratedImage

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

public void InitiateGenerateImages(List<Coordinate> coordinates)   
{
  var dispatcher = Dispatcher.CurrentDispatcher;

  var generatorThreadStarter = new ThreadStart(() =>
     GenerateImages(coordinates, dispatcher));

  ...

如果知道只在运行中的应用程序中使用此方法,并且应用程序只有一个UI线程,则只需调用Application.Current.Dispatcher得到当前调度员。缺点是:

  1. 失去了独立于构造的应用程序对象使用视图模型的能力。
  2. 应用程序中只能有一个UI线程。

在生成器线程中,在生成映像后添加一个冻结调用,然后使用Dispatcher转换到UI线程来设置映像:

var backgroundThreadImage = GenerateImage(coordinate);

backgroundThreadImage.Freeze();

dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
   GeneratedImage = backgroundThreadImage;
}));

在上面的代码中,重要的是Dispatcher.CurrentDispatcher可以从UI线程访问,而不是从生成器线程访问。每个线程都有自己的调度程序。如果从生成器线程调用Dispatcher.CurrentDispatcher。

换句话说,你必须这样做:

  var dispatcher = Dispatcher.CurrentDispatcher;

  var generatorThreadStarter = new ThreadStart(() =>
     GenerateImages(coordinates, dispatcher));

而不是这个:

  var generatorThreadStarter = new ThreadStart(() =>
     GenerateImages(coordinates, Dispatcher.CurrentDispatcher));

扫码关注云+社区