C# 递归WinRT异步问题

C# 递归WinRT异步问题,c#,asynchronous,recursion,windows-runtime,async-await,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()

我有一些代码可以执行如下操作:

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);
        }
    }
}
我似乎(有时)会遇到wierd计时问题,最终会挂起GetFileAsync(…)上的代码。这是由调用的递归性质引起的吗?当我将所有Wait调用更改为实际执行.Wait()以等待它们完成,并从本质上消除所有调用的异步性质时,我的代码执行得很好

这是由调用的递归性质引起的吗?当我将所有Wait调用更改为实际执行.Wait()以等待它们完成,并从本质上消除所有调用的异步性质时,我的代码执行得很好

这真的取决于-

最可能的罪魁祸首是调用方以某种方式阻塞用户界面线程(通过调用Wait()等)。在本例中,wait的默认行为是捕获调用同步上下文,并将结果发布回该上下文

但是,如果调用方正在使用该上下文,则可能会出现死锁

这很可能是由以下代码引起的:

Data(string name, bool load) { if (load) { Load.Wait(); }
通过使库代码(如这个XmlData类)显式地不使用调用同步上下文,可以很容易地避免这种情况。这通常仅适用于用户界面代码。通过避免捕获,你可以做两件事。首先,您可以提高总体性能(通常是显著的),其次,您可以避免这种死锁情况

这可以通过如下方式使用和更改代码来实现:

override async Task Load()
{
    var file = await GetFileAsync.(...).ConfigureAwait(false);
    var xmlDoc = await LoadXmlDocAsync(file).ConfigureAwait(false);
    ProcessXml(xmlDoc);
}
话虽如此,我还是会重新考虑一下这个设计。这里确实有两个问题

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

第二,将整个异步操作转换为同步操作,方法是将其与块一起放入构造函数中。相反,我建议重新思考这整件事


也许您可以修改它来创建某种形式的工厂,它可以异步返回加载的数据?这可能很简单,比如将用于创建的公共api设置为一个返回
任务的工厂方法,或者甚至是一个通用的
公共异步任务创建(字符串名称),其中TData:Data
方法,这将允许您保持构造和加载异步,并完全避免阻塞。

当我连接调试器并中断时,它只是坐着等待加载。Wait()。我现在没带。但就我所记得的,没有其他的执行。它只是在等待加载,但似乎没有其他事情发生。@gamernb如果是这样的话,这几乎可以保证在等待捕获的上下文时会死锁。请看我的答案。在构造函数中调用虚拟方法通常是一个坏主意(参见示例)是“Load.Wait();”实际上编码为“Load().Wait();”?我认为在第一个
任务上调用
configurewait()
就足够了,因为第二行将在没有同步上下文的情况下执行。但是这样做可能会更加健壮(例如,如果我决定稍后同步执行第一个操作)。@svick True-大部分情况下。我更喜欢这样,因为如果你在两者之间引入任何可能改变这种行为的东西会更容易。。。另外,如果
GetFileAsync
已经完成并同步运行(即:如果它正在使用缓存的任务),则第二个
ConfigureAwait
仍然是必需的。@svick这只是因为不能保证第一个任务会更改上下文,因为它总是可以同步完成,并且永远不会切换状态。我有机会对该调用路径中的所有等待进行一次快速测试,以调用ConfigureAwait(false)。这似乎没有什么不同。但是我再也没有机会调试了。