2012-03-06 47 views
2

我有一些代码,做这样的事情:递归的WinRT异步问题

abstract class Data 
{ 
    Data(string name, bool load) { if (load) { Load().Wait(); } 
    abstract Task Load(); 
} 

class XmlData : Data 
{ 
    XmlData(string name, bool load = true) : base(name, load) {} 
    override async Task Load() 
    { 
     var file = await GetFileAsync(...); 
     var xmlDoc = await LoadXmlDocAsync(file); 
     ProcessXml(xmlDoc); 
    } 
    void ProcessXml(XmlDocument xmlDoc) 
    { 
     foreach (var element in xmlDoc.Nodes) 
     { 
      if (element.NodeName == "something") 
       new XmlData(element.NodeText); 
     } 
    } 
} 

我似乎(有时)获得怪异的计时问题,它结束了在GetFileAsync(...)吊码。这是由调用的递归性质引起的吗?当我改变所有的等待调用来实际完成一个.Wait()来完成时,基本上摆脱了调用的所有异步性质,我的代码执行得很好。

+0

当我连接调试器并中断时,它只是坐着等待Load.Wait()。我现在没有和我在一起。但据我记忆,没有其他任何执行。它只是在等待Load,但没有其他事情似乎正在进行。 – 2012-03-06 19:30:22

+0

@gamernb如果是这样的话,这几乎可以保证在捕获的上下文中等待死锁。看到我的答案。 – 2012-03-06 19:31:46

+1

在构造函数中调用虚拟方法通常是一个坏主意(请参阅http:// stackoverflow。com/a/448272/224370例如) – 2012-03-06 19:54:02

回答

3

这是由调用的递归性质引起的吗?当我改变所有的等待调用来实际完成一个.Wait()来完成时,基本上摆脱了调用的所有异步性质,我的代码执行得很好。

这真的取决于 -

最有可能的罪魁祸首是,如果您的来电者以某种方式阻止用户界面线程(通过调用wait(),等)。在这种情况下,await的默认行为是捕获调用同步上下文,并将结果发回该上下文。

但是,如果调用者正在使用该上下文,则可能会发生死锁。

这是极有可能的情况下,和这行代码所引起:

Data(string name, bool load) { if (load) { Load.Wait(); } 

这可以通过您的库代码(这样XMLDATA类)可以很容易地避免明确使用调用同步上下文。这通常仅用于用户界面代码。通过避免捕获,你做了两件事。首先,你提高整体表现(通常戏剧性),其次,你避免这种死锁状态。

这可以通过使用ConfigureAwait和不断变化的,像这样的代码来完成:

override async Task Load() 
{ 
    var file = await GetFileAsync.(...).ConfigureAwait(false); 
    var xmlDoc = await LoadXmlDocAsync(file).ConfigureAwait(false); 
    ProcessXml(xmlDoc); 
} 

话虽这么说 - 我会重新考虑这个设计有点。这里真的有两个问题。

首先,你在你的构造函数中放置一个虚拟的方法调用,这是相当危险的,应尽可能地避免,因为这会导致不寻常的问题。

其次,您将整个异步操作转换为同步操作,方法是将其放入构造函数中。相反,我会推荐重新考虑这件事。

也许你可以重写这个来做出某种形式的工厂,它会异步加载你的数据?这可以像创建一个工厂方法的公共api一样简单,该方法返回Task<Data>,或者甚至是一个通用的public async Task<TData> Create<TData>(string name) where TData : Data方法,这将允许您保持异步构建和加载,并完全避免阻塞。

+0

我认为在第一个Task上调用'ConfigureAwait()'就足够了,因为第二行将在没有同步上下文的情况下执行。但是像这样做可能会更健壮(例如,如果我决定稍后同步执行第一个操作)。 – svick 2012-03-06 19:58:15

+0

@svick真 - 主要是。我更喜欢这个,因为如果你在两者之间引入了任何可能改变这种行为的东西,它会更容易......另外,如果'GetFileAsync'已经完成并同步运行(即:如果它使用缓存任务),那么第二个' ConfigureAwait'仍然是必要的。 – 2012-03-06 20:01:29

+0

@svick这只是因为不能保证第一个任务会改变上下文,因为它总是可以同步完成并且永远不会切换状态。 – 2012-03-06 20:02:25