torsdag 14. juli 2011

WPF, Notifications and Parallellism

I'm working on getting our GUI responsive, which can be quite a challenge. We fetch a pdf or tiff over a wcf-service from the test-server running on a vm, rendering the first page and showing this to the user. We also have a strict policy on separating as much as possible into the domain-layer, and the domain-layer can not have any gui-classes. Fetching and rendering is done in the domain-layer, and the GUI is notified through the INotifiyPropertyChanged-mechanisms.
I've had several problems with the ownership of wpf-objects when a task in the domain-level sets a property which the gui subscribes to, and the gui then tries to redraw some metainformation in an overlay canvas. I've solved several situations by using TPL's ContinueWith-mechanism where the new task is run on the GUI-thread.
After working on a problem today, where images are fetched every time the user changes the selected row in a table, I started thinking if there is a better way to do this. If I could only get all notifications to run on the GUI-thread, without having to use TPL's ContinueWith...
So I've now created a small test-project that has some classes, a window showing info, and some extension methods that run all the notifications on the GUI-thread.
This is the essence of the extension methods:
public static TaskScheduler GuiContext;

public static void Notify(this PropertyChangedEventHandler self, INotifyPropertyChanged sender, string propertyName) {
if (self == null) return;
Task.Factory.StartNew(() => self(sender, new PropertyChangedEventArgs(propertyName)), CancellationToken.None, TaskCreationOptions.None, GuiContext);
}
The GuiContext field needs to be set on startup from the MainWindow, e.g. in the Loaded-event. It appears as though the extension-methods works like a charm.
I made the test-app start several threads that updates the objects in the controller several times, and as long as I allow the gui some cpu-time(put in some small sleeps in the threads), the gui is responsive and it updates the textboxes with values from the different threads.
I'm aware that just updating the same values from several threads is not a good idea, but for showing data in the gui I'm really just interested in showing the latest values, and I have control over cancellelation on the background tasks in the real application. So for the purpose of the test-app I don't think this is important.

I'm writing this post in the hopes that someone will take a poke at my project and tell me if they find any flaws or large weaknesses that I've overlooked. Here is a link to the project file.

torsdag 10. februar 2011

Memory leaks while converting from bitmap to bitmapsource

One project I'm working on is using TwainDotNet. It has been some work to get to work "exactly" like we want it to, but it's overall been a nice library to play with.
But today, I've been working on a memory leak... These bugs are really annoying to work on, but anyhow here is what I found out:
The problem is that the sample code and samples I've found on the internet for converting Bitmap to BitmapSource. The code usually looks something like this:
    image1.Source = Imaging.CreateBitmapSourceFromHBitmap(
        new System.Drawing.Bitmap(args.Image).GetHbitmap(),
        IntPtr.Zero,
        Int32Rect.Empty,
        BitmapSizeOptions.FromEmptyOptions());

This works fine, but it also creates a HBitmap in unmanaged memory that it doesn't free.
A better way to solve this is to store the HBitmap in a variable and call DeleteObject on it. DeleteObject is a function from gdi32.dll:
    [System.Runtime.InteropServices.DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);

Here is the code I ended up with in our production code:
    private void TwainOnTransferImage(Object sender, TransferImageEventArgs e) {
        if (e.Image != null) {
            var bmp = new System.Drawing.Bitmap(e.Image);
            IntPtr hBitmap = bmp.GetHbitmap();
            image1.BeginInit();
            image1.Source = Imaging.CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
            image1.EndInit();
            image1.Source.Freeze();
            DeleteObject(hBitmap);
            bmp.Dispose();
            e.Image.Dispose();
        }
    }