Reputation: 1073
I have a WPF application that uses a component that sends a bitmap to my application as they become available, I receive those bitmaps in a delegate I pass to this component.
I created a new thread for this process and it works very well, the bitmaps comes as MemoryStream and I just create the BitmapSource object from this stream inside a Dispatcher.BeginInvoke
method call. After I have the BitmapSource object, I add them to a StackPanel so the user can see a queue of images available to work. So far so good...
The problem is that those bitmaps are quite big, like 3000x2000+ pixels, and it takes about 50~ms to create these bitmaps and add to the queue, and when this code is executed, the onde inside the BeginInvoke call, it blocks the UI for this time, causing a very annoying behavior, (to reproduce this, just call Thread.Sleep(50)
every 5 seconds).
How can I fix this so the user is always responsive?
thanks!
Upvotes: 3
Views: 2804
Reputation: 37819
Two possibilities come to mind quickly.
The first is to use a BackgroundWorker to perform the transformation of the MemoryStream to a Bitmap.
the Second is to pass off that transformation to a ThreadPool.
Upvotes: 1
Reputation: 15393
Really all you need to do is set the IsAsync to True on your binding to the image. I would however recomend using a PriorityBinding and preparing some form of default image that the user can see so they know it isn't fully loaded.
<StackPanel>
<Image>
<Image.Source>
<PriorityBinding>
<Binding Path="SlowImage"
IsAsync="True" />
<Binding Path="DefaultImage" />
</PriorityBinding>
</Image.Source>
</Image>
</StackPanel>
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
DefaultImage = new BitmapImage(new Uri("http://stackoverflow.com/content/img/so/logo.png"));
SlowImage = new BitmapImage(new Uri("http://serverfault.com/content/img/sf/logo.png"));
this.DataContext = this;
}
private BitmapImage myDefaultImage;
public BitmapImage DefaultImage
{
get { return this.myDefaultImage; }
set
{
this.myDefaultImage = value;
this.NotifyPropertyChanged("Image");
}
}
private BitmapImage mySlowImage;
public BitmapImage SlowImage
{
get
{
Thread.Sleep(5000);
return this.mySlowImage;
}
set
{
this.mySlowImage = value;
this.NotifyPropertyChanged("SlowImage");
}
}
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Upvotes: 1
Reputation: 24208
There are two ideas you might want to consider:
Don't create the BitmapSource inside Dispatcher.Invoke. That would practically create it inside the UI thread, slowing things down. Instead, create it in the background thread, freeze it, and then pass the frozen BitmapSource to the foreground thread.
Depending on your application, perhaps the StackPanel doesn't need the full 3000x2000 resolution? If that is the case, consider resizing down the images in the background thread, just before you freeze them.
The following code does #1 above:
Window1.xaml
<Window x:Class="BitmapFrameDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Image Name="image"/>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace BitmapFrameDemo {
public partial class Window1 : Window {
private Thread thread = null;
private Dispatcher dispatcher = null;
private void ThreadMain() {
// obtain the image memory stream
WebRequest request = WebRequest.Create("http://stackoverflow.com/content/img/so/logo.png");
WebResponse response = request.GetResponse();
Stream stream = response.GetResponseStream();
// create a bitmap source while still in the background thread
PngBitmapDecoder decoder = new PngBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapFrame frame = decoder.Frames[0];
// freeze the bitmap source, so that we can pass it to the foreground thread
BitmapFrame frozen = (BitmapFrame) frame.GetAsFrozen();
dispatcher.Invoke(new Action(() => { image.Source = frozen; }), new object[] { });
}
public Window1() {
InitializeComponent();
dispatcher = Dispatcher.CurrentDispatcher;
thread = new Thread(new ThreadStart(this.ThreadMain));
thread.Start();
}
}
}
alt text http://www.freeimagehosting.net/uploads/66bdbce78a.png
Upvotes: 4
Reputation: 17709
While not an entirely satifsfactory answer for you, I would suggest you listen to this Hanselminutes podcast with Ian Griffiths as it covers a very similar program and the proper way to architect it to get the performance you are looking for.
Some specifics may be found on Ian's blog: http://www.interact-sw.co.uk/iangblog/
Upvotes: 0