递归WinRT异步问题

时间:2012-03-06 19:23:21

标签: c# asynchronous recursion windows-runtime async-await

我有一些代码可以做这样的事情:

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(...)上。这是由呼叫的递归性质引起的吗?当我更改所有await调用实际执行.Wait()以使它们完成时,基本上摆脱了调用的所有异步性质,我的代码执行正常。

1 个答案:

答案 0 :(得分:3)

  

这是由呼叫的递归性质引起的吗?当我更改所有await调用实际执行.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方法,这样可以保持构造和加载异步并完全避免阻塞