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)。这似乎没有什么不同。但是我再也没有机会调试了。