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

时间:2021-02-11 20:58:37

I have a background thread that generates a series of BitmapImage objects. Each time the background thread finishes generating a bitmap, I would like to show this bitmap to the user. The problem is figuring out how to pass the BitmapImage from the background thread to the UI thread.

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

This is an MVVM project, so my view has an Image element:

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

<Image Source="{Binding GeneratedImage}" />

My view-model has a property GeneratedImage:

我的视图模型有一个属性生成:

private BitmapImage _generatedImage;

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

My view-model also has the code that creates the background thread:

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

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?
    }
}

I'd like to somehow pass backgroundThreadImage to the UI thread, where it will become uiThreadImage, then set GeneratedImage = uiThreadImage so the view can update. I've looked at some examples dealing with the WPF Dispatcher, but I can't seem to come up with an example that addresses this issue. Please advise.

我想把backgroundThreadImage传递给UI线程,在那里它将成为uiThreadImage,然后设置GeneratedImage = uiThreadImage以便视图可以更新。我已经看过一些与WPF分派器有关的示例,但是我似乎无法找到一个解决这个问题的示例。请建议。

3 个解决方案

#1


12  

The following uses the dispatcher to execute an Action delegate on the UI thread. This uses a synchronous model, the alternate Dispatcher.BeginInvoke will execute the delegate asynchronously.

下面使用dispatcher在UI线程上执行一个动作委托。这使用了一个同步模型,即备用分派器。BeginInvoke将异步执行委托。

var backgroundThreadImage = GenerateImage(coordinate);

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

UPDATE As discussed in the comments, the above alone will not work as the BitmapImage is not being created on the UI thread. If you have no intention of modifying the image once you have created it you can freeze it using Freezable.Freeze and then assign to GeneratedImage in the dispatcher delegate (the BitmapImage becomes read-only and thus threadsafe as a result of the Freeze). The other option would be to load the image into a MemoryStream on the background thread and then create the BitmapImage on the UI thread in the dispatcher delegate with that stream and the StreamSource property of BitmapImage.

如注释中所讨论的那样进行更新,由于没有在UI线程上创建BitmapImage,因此上面的操作将无法工作。如果您在创建映像之后没有修改映像的意图,您可以使用Freezable来冻结映像。冻结,然后在分派器委托中分配给GeneratedImage(位图映像由于冻结而变成只读的并因此成为threadsafe)。另一个选项是将图像加载到后台线程的MemoryStream中,然后在dispatcher委托的UI线程上使用该流和BitmapImage的StreamSource属性创建位映射映像。

#2


6  

You need to do two things:

你需要做两件事:

  1. Freeze your BitmapImage so it can be moved to the UI thread, then
  2. 冻结你的位图图像,以便它可以移动到UI线程
  3. Use the Dispatcher to transition to the UI thread to set the GeneratedImage
  4. 使用分派器转换到UI线程以设置GeneratedImage

You'll need to access the UI thread's Dispatcher from the generator thread. The most flexible way to do this is to capturing the the value Dispatcher.CurrentDispatcher in the main thread and passing it into the generator thread:

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

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

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

  ...

If you know you will only use this within a running Application and that the application will only have one UI thread, you can just call Application.Current.Dispatcher to get the current dispatcher. The disadvantages are:

如果您知道您将只在运行的应用程序中使用这个,并且该应用程序只有一个UI线程,那么您只需调用Application. current。获取当前分派器的分派器。缺点是:

  1. You loose the ability to use your view model independently of a constructed Application object.
  2. 您失去了独立于构造的应用程序对象使用视图模型的能力。
  3. You can only have one UI thread in your application.
  4. 在应用程序中只能有一个UI线程。

In the generator thread, add a call to Freeze after the image is generated, then use the Dispatcher to transition to the UI thread to set the image:

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

var backgroundThreadImage = GenerateImage(coordinate);

backgroundThreadImage.Freeze();

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

Clarification

澄清

In the above code it is critical that Dispatcher.CurrentDispatcher be accessed from the UI thread, not from the generator thread. Every thread has its own Dispatcher. If you call Dispatcher.CurrentDispatcher from the generator thread you will get its Dispatcher instead of the one you want.

在上面的代码中,分派器是至关重要的。从UI线程访问CurrentDispatcher,而不是从生成器线程访问。每个线程都有自己的分派器。如果你调用调度程序。来自生成器线程的CurrentDispatcher将得到它的Dispatcher而不是您想要的。

In other words, you must do this:

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

  var dispatcher = Dispatcher.CurrentDispatcher;

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

and not this:

而不是:

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

#3


0  

In the background thread work with Streams.

在后台线程处理流。

For example, in the background thread:

例如,在后台线程:

var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative);

StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri);

var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream;

SendStreamToDispatcher(_defaultArtPlaceholderStream);

In the UI thread:

在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!
     })); 
}

#1


12  

The following uses the dispatcher to execute an Action delegate on the UI thread. This uses a synchronous model, the alternate Dispatcher.BeginInvoke will execute the delegate asynchronously.

下面使用dispatcher在UI线程上执行一个动作委托。这使用了一个同步模型,即备用分派器。BeginInvoke将异步执行委托。

var backgroundThreadImage = GenerateImage(coordinate);

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

UPDATE As discussed in the comments, the above alone will not work as the BitmapImage is not being created on the UI thread. If you have no intention of modifying the image once you have created it you can freeze it using Freezable.Freeze and then assign to GeneratedImage in the dispatcher delegate (the BitmapImage becomes read-only and thus threadsafe as a result of the Freeze). The other option would be to load the image into a MemoryStream on the background thread and then create the BitmapImage on the UI thread in the dispatcher delegate with that stream and the StreamSource property of BitmapImage.

如注释中所讨论的那样进行更新,由于没有在UI线程上创建BitmapImage,因此上面的操作将无法工作。如果您在创建映像之后没有修改映像的意图,您可以使用Freezable来冻结映像。冻结,然后在分派器委托中分配给GeneratedImage(位图映像由于冻结而变成只读的并因此成为threadsafe)。另一个选项是将图像加载到后台线程的MemoryStream中,然后在dispatcher委托的UI线程上使用该流和BitmapImage的StreamSource属性创建位映射映像。

#2


6  

You need to do two things:

你需要做两件事:

  1. Freeze your BitmapImage so it can be moved to the UI thread, then
  2. 冻结你的位图图像,以便它可以移动到UI线程
  3. Use the Dispatcher to transition to the UI thread to set the GeneratedImage
  4. 使用分派器转换到UI线程以设置GeneratedImage

You'll need to access the UI thread's Dispatcher from the generator thread. The most flexible way to do this is to capturing the the value Dispatcher.CurrentDispatcher in the main thread and passing it into the generator thread:

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

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

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

  ...

If you know you will only use this within a running Application and that the application will only have one UI thread, you can just call Application.Current.Dispatcher to get the current dispatcher. The disadvantages are:

如果您知道您将只在运行的应用程序中使用这个,并且该应用程序只有一个UI线程,那么您只需调用Application. current。获取当前分派器的分派器。缺点是:

  1. You loose the ability to use your view model independently of a constructed Application object.
  2. 您失去了独立于构造的应用程序对象使用视图模型的能力。
  3. You can only have one UI thread in your application.
  4. 在应用程序中只能有一个UI线程。

In the generator thread, add a call to Freeze after the image is generated, then use the Dispatcher to transition to the UI thread to set the image:

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

var backgroundThreadImage = GenerateImage(coordinate);

backgroundThreadImage.Freeze();

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

Clarification

澄清

In the above code it is critical that Dispatcher.CurrentDispatcher be accessed from the UI thread, not from the generator thread. Every thread has its own Dispatcher. If you call Dispatcher.CurrentDispatcher from the generator thread you will get its Dispatcher instead of the one you want.

在上面的代码中,分派器是至关重要的。从UI线程访问CurrentDispatcher,而不是从生成器线程访问。每个线程都有自己的分派器。如果你调用调度程序。来自生成器线程的CurrentDispatcher将得到它的Dispatcher而不是您想要的。

In other words, you must do this:

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

  var dispatcher = Dispatcher.CurrentDispatcher;

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

and not this:

而不是:

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

#3


0  

In the background thread work with Streams.

在后台线程处理流。

For example, in the background thread:

例如,在后台线程:

var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative);

StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri);

var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream;

SendStreamToDispatcher(_defaultArtPlaceholderStream);

In the UI thread:

在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!
     })); 
}