0
votes

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;
    }
2
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 awaiting anything either... - Patryk Ćwiek
sorry, missed that await copying the code. So, how do I run things in the background? - Thomas
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
not really sure how to do this exactely. I'm pretty new to C# if you haven't noticed ;-) - Thomas

2 Answers

1
votes

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;
    }
1
votes

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