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

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.


Ray*_*rns 8

你需要做两件事:

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

您需要从生成器线程访问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来获得当前调度.缺点是:

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

在发电机螺纹,添加一个呼叫到冻结生成图像后,再使用分派器转变到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)