2

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

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() 以使它们完成时,并且基本上摆脱了调用的所有异步性质,我的代码执行得很好。

4

1 回答 1

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);
}

话虽如此 - 我会重新考虑这个设计。这里确实有两个问题。

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

其次,通过将 this 放入带有块的构造函数中,您可以将整个异步操作转换为同步操作。相反,我建议重新考虑整个事情。

也许您可以重新设计它以制作某种形式的工厂,它会返回异步加载的数据?这可以简单到将用于创建的公共 api 创建为返回的工厂方法Task<Data>,或者甚至是通用public async Task<TData> Create<TData>(string name) where TData : Data方法,这将允许您保持构造和加载异步并完全避免阻塞。

于 2012-03-06T19:31:14.940 回答