ValueConverter for StorageFile => Image

0

I'm trying to use MVVM for a Universal Windows project but the interfaces for Storage File complains a lot about using async. The following code sometimes works:

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var storageFile = value as StorageFile;
        return GetImageAsync(storageFile).Result;
    }

    private static async Task<ImageSource> GetImageAsync(StorageFile storageFile)
    {
        var bitmapImage = new BitmapImage();
        var stream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask().ConfigureAwait(false);
        bitmapImage.SetSource(stream);
        return bitmapImage;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return null;
    }
}

Until I select a new image to load, then I get the error "{"The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))"}"

So I tried changing it to use the CoreDispatcher per another thread:

public class FileToImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var storageFile = value as StorageFile;
        Task<ImageSource> image = null;
        Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            image = GetImageAsync(storageFile);
            image.RunSynchronously();
        });

        return image.Result;
    }

    private static async Task<ImageSource> GetImageAsync(StorageFile storageFile)
    {
        var bitmapImage = new BitmapImage();
        var stream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask().ConfigureAwait(false);
        bitmapImage.SetSource(stream);
        return bitmapImage;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return null;
    }
}

NullReferenceException on bitmapimage. This makes absolute sense to me of course - the async dispatcher cedes control to the parent process, image has not been assigned, null reference exception. But I don't know what the right way is!

c#
async-await
uwp
win-universal-app
storagefile
asked on Stack Overflow Dec 3, 2016 by C Bauer • edited Dec 3, 2016 by C Bauer

1 Answer

0

Thank you Clemens for your comments on OP, which made me realize that I was pattern-obsessing and focused on using something I liked instead of doing the right design.

The source that was causing the issue is this:

    private async void GetFile()
    {
        var filePicker = new FileOpenPicker();
        filePicker.FileTypeFilter.Add(".jpg");
        filePicker.FileTypeFilter.Add(".png");
        filePicker.FileTypeFilter.Add(".gif");
        filePicker.FileTypeFilter.Add(".bmp");
        filePicker.ViewMode = PickerViewMode.Thumbnail;
        filePicker.SuggestedStartLocation = PickerLocationId.Desktop;
        filePicker.CommitButtonText = "Open";
        CurrentFile = await filePicker.PickSingleFileAsync(); //Bad code used CurrentFile set and NotifyPropertyChanged to start up the value converter code from OP

        //New, obvious better code            
        CurrentImage = await GetImageSource(CurrentFile);

        var statistics = new ImageStatistics();

        Logger.Log("Metadata start");
        var data = statistics.GetMetaData(CurrentFile);

        Logger.Log("Color Counts start");
        var colorCounts = statistics.GetColorCounts(CurrentFile);

        var filterer = new ColorFilterer();
        Logger.Log("Color Counts await start");
        var filteredColors = filterer.GetTopColors(await colorCounts, 10);
        Logger.Log("Color Counts await end");

        Logger.Log("Metadata await start");
        var metaData = await data;
        Logger.Log("Metadata await end");

        Make = metaData[SystemProperty.CameraManufacturer];
        Model = metaData[SystemProperty.CameraModel];
        ExposureTime = string.Format("1/{0}",1/Convert.ToDouble(metaData[SystemProperty.ExposureTime]));
        ISOSpeed = metaData[SystemProperty.ISOSpeed];
        FStop = string.Format("f/{0}", metaData[SystemProperty.FStop]);
        ExposureBias = metaData[SystemProperty.ExposureBias];

        TopColors = filteredColors.Select(pair => new ColorStatistics { Color = pair.Key, Count = pair.Value }).ToList();
    }

So I just continue performing the operations that I wanted on the image after its been selected. There's still a lot to fix here, especially since I'm blocking UI while performing work and not delegating these operations by subscribing these other UI components to the property, but it's a start, and no more exceptions!

Note that not included here, the value converter has been removed from the main application layer.

answered on Stack Overflow Dec 4, 2016 by C Bauer • edited May 23, 2017 by Community

User contributions licensed under CC BY-SA 3.0