为什么异步连续方法会停止运行



我有一个连续的异步方法,用于轮询资源和消息队列等操作。

private async Task MonitorAsync(CancellationToken cancelToken)
{
while (!cancelToken.IsCancellationRequested)
{
await Task.Delay(100);
// Poll stuff and take action
}
}

这在 UIContext 中运行(简化了并发问题)。

AppViewModel()
{
MonitorAsync(); // Starts the Monitor
}

我观察到的是,在某些极端条件下,这种异步方法只会停止运行(例如,应用程序停止处理消息)。例如,如果在 UIContext 中运行过多的 CPU 密集型代码。此外,我有几个监视器正在运行,只有其中一些死了。

我承认,到目前为止,我只看到这种情况发生在从根本上需要解决的场景中,但我仍然担心在边缘情况下它仍然是可能的。

作为一种解决方法,我可能需要添加一个计时器,如果显示器似乎已经死了,我可能需要重新启动它。

一些附加说明:

  • 它不会引发异常。挂接到 UnobservedTaskException 或包装 try/catch 可以确认这一点。
  • 它绝对不会停留在内部等待中。我添加了一个简单的标志来确认,它得到"等待任务.延迟(100)",但它永远不会回来。

问题

  1. 什么可能导致这种情况发生?
  2. 我将如何调试它?我可以检查哪个对象,例如,查看给定上下文中正在运行的异步方法的列表?

我怀疑Visual Studio 2015"任务窗口"应该列出所有异步方法,但它是空白的。它说"没有要显示的任务"。 我从未见过它显示任何东西。

更多信息:

我确定它没有抛出异常或陷入无限期等待。症状似乎是任务不再运行(或延迟了很长时间)。它似乎还继续运行一些任务,实际上仍在运行的任务是较新的任务。

我有一个理论,在这种罕见的情况下,会优先考虑最近创建的任务。它为最近创建的那些提供了有限的时间,而旧的实际上不再运行。

如果我可以访问任务列表,这将有所帮助。然后我可以确认是否是这种情况。我可以为线程执行此操作,但到目前为止,我还没有找到如何为任务执行此操作。

更新 6/7/2016:

似乎有问题的任务实际上仍在运行。它只是大大延迟了。例如,如果我运行此方法的三个任务,然后重新创建边缘情况 - 其中两个运行良好(等待在 100 毫秒后恢复),但其中一个(最旧的一个)需要 2 秒到 20  秒(有时更多)。因此,调度程序似乎没有尝试公平地分配有限的处理可用性。

根据建议,我将分为两个明确陈述的问题:

  1. 如何访问任务列表
  2. 调度程序应该如何表现

部分答案

1. 什么可能导致这种情况发生?

我无法找出调度程序如何管理任务,但观察表明,当调度程序落后时,由此产生的任务调度甚至不接近公平。某些任务可能会明显延迟,并且似乎确实偏向于较新的任务。例如,在一个具有三个相同任务的测试中,这些任务预计每 100 毫秒处理一次:

  • 最近的两个仍然每~150 毫秒持续运行一次
  • 最旧的延迟从 500 毫秒到 30 秒不等

阿拉伯数字。我将如何调试它?我可以检查哪个对象,例如,查看给定上下文中正在运行的异步方法的列表?

我无法弄清楚如何使用对象检查来获取任务列表,而是获取任务列表或查看队列的最简单方法。VS2015中的"并行堆栈"或"任务"窗口是查看所有任务的绝佳工具,但有以下限制:

  • 它不会列出 Windows 7 中异步方法中的任务
  • 它不会为您提供有关队列或优先级的任何信息。您可以随时添加自己的计时器来猜测它。

如果你没有等待任务,也从不调用Wait()ResultGetAwaiter()ContinueWith(),你怎么知道它在垃圾回收之前被安排在任何地方?

是否更改此行:

MonitorAsync(); // starts the Monitor

对此

MonitorAsync().ContinueWith(t => Console.WriteLine("monitor ended")); // starts the Monitor

导致它开始运行?

编辑:这可能不太可能是问题 - 根据 MSDN 文档"调用异步方法时,它会同步执行函数的主体,直到尚未完成的可等待实例上的第一个 await 表达式,此时调用返回到调用方Task。 当然,直到方法的第一个await点或结束之前的所有内容都会运行。我将其与创建具有new Task(...)Task混为一谈,然后不在任务计划程序上计划它。await内部的Task.Delay应该足以将方法的其余部分注册为延续并确保它将被执行。

我认为只有两种选择:

  1. 该方法引发异常。您已经说过它没有,但是也许您可以通过在调试器下运行应用程序来仔细检查,同时为所有CLR异常启用"抛出时中断"?
  2. UI 线程非常繁忙,以至于在应该恢复时永远无法运行您的方法。由于这也意味着 UI 将停止响应,而您没有说它会停止响应,因此这对我来说听起来不是一个可能的选择。

最新更新