很明显,async/await有一些我不理解的地方。
以下代码有什么问题?它在异步任务中创建FDecoder
对象。但在那之后,每当我试图访问FDecoder字段时,我都会得到一个InvalidOperation异常,声明该对象由另一个线程所有。我认为等待很酷,我可以将结果返回到调用线程。。。?
//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
}
编辑:
好的,我发现Task创建的实际GifBitmapDecoder对象是一个具有线程亲和性的DispatcherObject。这是主要问题。。。似乎唯一的方法是从异步任务中的Dispatcher对象中获取所有需要的数据,并在没有线程关联的情况下传递回一个普通对象。但如果有人知道更好的方法,请告诉我。
您总是回到同一个上下文中,但并非所有上下文都绑定到一个线程。值得注意的是,线程池上下文将所有线程池线程视为相等。
但我不认为这是这里的具体问题——您使用的是Task.Run()
,它旨在在线程池中运行代码。因此,即使await
将所有内容都切换回UI上下文,也没关系,因为您在线程池中显式地运行了一些代码。
这是一个有趣的问题,因为(正如您正确指出的)GifBitmapDecoder
继承自DispatcherObject
。这意味着它的实现不允许任何线程调用其操作。
要使用任何DispatcherObject
,您应该通过其Dispatcher
属性进行调用。返回的Dispatcher
对象使您可以通过InvokeAsync
:以与其内部线程模型兼容的方式针对真实对象调度委托
var decoder = new GifBitmapDecoder(...);
var operation = decoder.Dispatcher.InvokeAsync(() => { }); // Do things here!
这种模式不是返回TPLTask
而是返回DispatcherOperation
(可能是因为它早于TPL)。这个类似任务的对象可以让您检查操作的状态并获得任何结果。它也是可用的,这意味着你可以像使用TPLTask
:一样使用await
await decoder.Dispatcher.InvokeAsync(() => { });
在您的特定问题中,您应该在OpenCompleted()
方法中使用此模式。您可能希望将其设为OnCompletedAsync()
并返回一个Task
,以使您能够捕获用于延续的UI同步上下文,并让TPL处理从Dispatcher
到UI线程的编组调用。
public async void OpenFileAsync(string filename, bool forceReload = false)
{
FDecoder = await OpenFileTask(filename, forceReload);
await OpenCompletedAsync();
}
Task.Run()
调度到线程池,因此您的GifBitmapDecoder
是在另一个线程上创建的。
OpenFileTask正在返回一个task<GifBitMapDecoder>
。您可能需要
Task <GifBitMapDecoder> t = OpenFileTask();
Fdecoder = t.result; //Returns the GifBitMapDecoder object.
虽然对异步的东西不太了解,但可能和你的一样
来源:C#5.0。