0

I'm currently writing a small Image editing app for the Windows Store (WinRT) using the great WriteableBitmapEx Framework. As functions like .convolute can take a while on WinRT devices (tested on Surface) I'd like to make those requests async so the UI doesn't get blocked and I can show a progress ring.

This is what I've tried so far, an the code itself is working. But the UI still gets blocked and the ring does not show. The code does take about 2 seconds to execute.

// Start Image editing when selection is changed
private async void FilterListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        progressRing.IsActive = true;

        try
        {
            filteredImage = await FilterMethod.imageBW(originalImage, filteredImage);

        }
        catch
        {
            Debug.WriteLine("No items selected");
        }

        mainImage.Source = filteredImage;
        progressRing.IsActive = false;
    }


    // Black & White
    public static async Task<WriteableBitmap> imageBW(WriteableBitmap originalImage, WriteableBitmap filteredImage)
    {
        filteredImage = originalImage.Clone();

        using (filteredImage.GetBitmapContext())
        {
            filteredImage.ForEach(ImageEdit.toGrayscale);
        }

        return filteredImage;
    }


    // Grayscale
    public static Color toGrayscale(int x, int y, Color color)
    {
        byte gray = (byte)(color.R * .3f + color.G * .59f + color.B * .11f);
        Color newColor = Color.FromArgb(255, gray, gray, gray);
        return newColor;
    }
Thomas
  • 4,030
  • 4
  • 40
  • 79
  • You are aware that adding `async` to the method does not automagically make it asynchronous by running things in background, right? And you're not `await`ing anything either... – Patryk Ćwiek Jun 16 '13 at 19:29
  • sorry, missed that await copying the code. So, how do I run things in the background? – Thomas Jun 16 '13 at 19:57
  • Easiest way would be to wrap the whole method that converts the image to grayscale in a `Task` so it runs in a background and `await` the result. – Patryk Ćwiek Jun 16 '13 at 20:05
  • not really sure how to do this exactely. I'm pretty new to C# if you haven't noticed ;-) – Thomas Jun 16 '13 at 23:04

2 Answers2

1

OK, as I mentioned, adding async to a method does not make it automagically do things in asynchronous fashion. It just means that the compiler will turn it to a state machine, which makes writing continuations easier.

The easiest way to process it in a background is to wrap the computation in a Task. I'm not quite sure how cross-thread marshalling looks like with bitmaps though:

private async void FilterListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        progressRing.IsActive = true;

        try
        {
            var filteredImage = await Task.Run(() => 
                            {
                                var clonedBitmap = originalImage.Clone();

                                using (clonedBitmap.GetBitmapContext())
                                {
                                   clonedBitmap.ForEach(ImageEdit.toGrayscale);
                                }

                                 return clonedBitmap;
                           });

            mainImage.Source = filteredImage;
        }
        catch
        {
            Debug.WriteLine("No items selected");
        }    

        progressRing.IsActive = false;
    }
Patryk Ćwiek
  • 14,078
  • 3
  • 55
  • 76
  • thx. Unfortunately I get following error trying to execute that code: "application called an interface that was marshalled for a different thread" From what I've read it seems that the UI thread is required to do that kind of image manipulation. I got it working now however using Window.Current.Dispatcher.RunAsync – Thomas Jun 17 '13 at 16:23
  • 1
    @Thomas Yep, that's it. That's what I mentioned by 'I'm not quite sure how cross-thread marshalling looks like with bitmaps' :) – Patryk Ćwiek Jun 17 '13 at 18:33
1

As this kind of image editing seems to be needing to happen on the UI thread, I was able to wrap my code inside a

await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {}

block, so now it looks like this:

private async void FilterListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    progressRing.IsActive = true;

    try
    {
        await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            filteredImage = await FilterMethod.imageBW(originalImage, filteredImage);
        }

    }
    catch
    {
        Debug.WriteLine("No items selected");
    }

    mainImage.Source = filteredImage;
    progressRing.IsActive = false;
}
Thomas
  • 4,030
  • 4
  • 40
  • 79