1
votes

I have a Windows 8 store app in XAML. I have an Image control that binds to a property, but because the URL I want to display requires authentication, I have to create a webresponse and generate the bitmap that way instead of just providing a URL to my image control.

Problem is, the memorystream operations are async, and properties on an object have to be sync, not async. So I have a pretty simple setup:

public ImageSource ImageSource
{
    get { return Task.Run(() => BitmapImageUtils.ToImage(this.Upload.ThumbFile)).Result; }

and the Image control has the ImageSource property as its binding. Problem is, I'm receiving the below exception. there's multiple Image controls in a ListView and they're all binding in this way, and my guess is that the UI thread that invokes this somehow hands off control to a thread then tries to come back somehow. I'm a little new to this.

The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

Any help is appreciated.

--- EDIT ToImage Method

public static async Task<BitmapImage> ToImage(byte[] byteArray)
    {
        var bitmapImage = new BitmapImage();

        var stream = new InMemoryRandomAccessStream();
        await stream.WriteAsync(byteArray.AsBuffer());
        stream.Seek(0);

        await bitmapImage.SetSourceAsync(stream);
        return bitmapImage;
    }

Exception (Note the exception is an inner exception of an aggregate exception, which I believe is pretty standard for async/await exceptions

at Windows.UI.Xaml.Media.Imaging.BitmapImage..ctor()
at Campfire.Utils.BitmapImageUtils.<ToImage>d__0.MoveNext()
2
Side note: property that executes network operation feels very wrong... What if it will be called more often than you expect? - Alexei Levenkov
Agreed, its not ideal. But I can't think of a way to show an image control on demand when the image is behind an authentication barrier. (requires a special header in the web request with an auth token). Plus in a listview the image isn't loaded until the item is in view, which in my case performs pretty well. - Richthofen
Side note: check out this question as it may be useful - Make WPF Image load async. - Alexei Levenkov
It looks like the isasync won't work in Windows RT - Richthofen
What is the stack trace of the exception? And the code on ToImage might be useful too. - svick

2 Answers

4
votes

The core problem is that many UI types (including BitmapImage) have UI thread affinity, and your current code is trying to create them on a background thread (in Task.Run).

I recommend that you redesign your property; you shouldn't have it blocking on an I/O operation anyway. I have a type in my AsyncEx library that allows you to essentially data-bind to the results of a Task<T>.

Your property becomes:

public INotifyTaskCompletion<ImageSource> ImageSource { get; private set; }

And when you want to start downloading, you do:

ImageSource = NotifyTaskCompletion.Create(BitmapImageUtils.ToImage(Upload.ThumbFile)));

Your databinding then changes from ImageSource to ImageSource.Result. There are other properties as well (e.g., ImageSource.IsFaulted, ImageSource.ErrorMessage) that allow you to handle other results with data binding. Note that INotifyTaskCompletion<T>.Result is not blocking; it will just return the default value until the task completes.

P.S. AggregateException is not normal in async/await. The only reason you were seeing it is because you were using Task<T>.Result; if you await the same task, you'll get an exception that is not wrapped in AggregateException.

0
votes

Why not add a LoadImage to the object with the property on it, and get that to do the loading in an async manner. Once it's finished loading everything it can set the ImageSource property normally.