Question

Clearly, there is something I am not understandig with async/await.

What is wrong with the following code? It creates the FDecoder object in an async task. But after that, whenever I try to access the FDecoder field I get an InvalidOperation exception stating that the object is owned by another thread. I thought that's the cool thing about await, that i get the results back into the calling thread...?

//could take very long for image from web
private Task<GifBitmapDecoder> OpenFileTask(string filename, bool forceReload = false)
{
    return Task.Run(() => 
        {
            return new GifBitmapDecoder(new Uri(filename, UriKind.RelativeOrAbsolute), forceReload ? BitmapCreateOptions.IgnoreImageCache : BitmapCreateOptions.None, BitmapCacheOption.Default);
        });
}

GifBitmapDecoder FDecoder;
public async void OpenFileAsync(string filename, bool forceReload = false)
{
    FDecoder = await OpenFileTask(filename, forceReload);
    OpenCompleted(); // do stuff with FDecoder field, throws invalid thread exception
}

EDIT:

Ok, what i found out is that the actual GifBitmapDecoder object the Task creates is a DispatcherObject which has thread affinity. This is the main problem... It appears that the only way is to get all needed data out of the Dispatcher object in the async task and pass back a normal object without thread affinity. But if anyone knows a better method, please tell me.

Was it helpful?

Solution 2

This is an interesting problem, because (as you rightly point out) GifBitmapDecoder inherits from DispatcherObject. This means it has an implementation which does not allow just any thread to invoke its operations.

To work with any DispatcherObject you should make calls through its Dispatcher property. The returned Dispatcher object lets you schedule delegates against the real object in a way that's compatible with its internal threading model via InvokeAsync:

var decoder = new GifBitmapDecoder(...);
var operation = decoder.Dispatcher.InvokeAsync(() => { }); // Do things here!

This pattern, rather than returning a TPL Task returns a DispatcherOperation (presumably because it pre-dates TPL). This very task-like object lets you examine the state of the operation and get any results. It's also awaitable, meaning you can use it with await just like a TPL Task:

await decoder.Dispatcher.InvokeAsync(() => { });

In your specific problem, you should use this pattern in your OpenCompleted() method. You will probably want to make it OnCompletedAsync() and return a Task to enable you to capture the UI Synchronization Context for your continuations and let the TPL handle marshalling calls back from the Dispatcher to the UI thread.

public async void OpenFileAsync(string filename, bool forceReload = false)
{
    FDecoder = await OpenFileTask(filename, forceReload);
    await OpenCompletedAsync();
}

OTHER TIPS

You always end up back in the same context, but not all contexts are tied to a single thread. Notably, the Thread Pool context treats all thread pool threads as being equal.

But I don't think that that's the specific issue here - you're using Task.Run() which is meant to run code in the thread pool. So even if your await switches everything back into the UI context, it doesn't matter because you run some of the code, explicitly, in the thread pool.

Task.Run() schedules to the threadpool, so your GifBitmapDecoder is being created on a different thread.

OpenFileTask is returning a task<GifBitMapDecoder>. You will likely need

Task <GifBitMapDecoder> t = OpenFileTask();

Fdecoder = t.result; //Returns the GifBitMapDecoder object.

Don't know much about the async stuff though, but probably is the same as you have it.

Source:C#5.0 in a nutshell.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top