【问题标题】:Why would an async continuous method stop running? [closed]为什么异步连续方法会停止运行? [关闭]
【发布时间】:2017-03-25 06:22:07
【问题描述】:

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

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 绑定代码。此外,我有几个监视器在运行,但只有一些监视器死了。

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

作为一种解决方法,我可能需要添加一个计时器并在 Monitor 似乎已经死机时重新启动它。

一些补充说明:

  • 它没有抛出异常。挂钩 UnobservedTaskException 或包装在 try/catch 中可以确认这一点。
  • 绝对不会卡在内部等待中。我添加了一个简单的标志来确认,它进入“等待 Task.Delay(100)”,但它再也没有回来。

问题

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

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

更多信息:

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

我有一个理论,在这种罕见的情况下,会优先考虑最近创建的任务。它将尽可能有限的时间分配给最近创建的那些,而旧的实际上不再运行。

如果我可以访问任务列表会有所帮助。然后我可以确认是否是这种情况。我可以为线程做到这一点,但到目前为止我还没有找到如何为任务做到这一点。

2016 年 6 月 7 日更新:

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

根据建议,我将分成两个明确说明的问题:

  1. 如何访问任务列表
  2. 调度程序的行为方式

部分答案

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

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

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

2.我将如何调试这个?例如,我可以检查哪些对象以查看给定上下文中正在运行的异步方法的列表?

我无法弄清楚如何使用对象检查来获取任务列表,但最简单的方法是获取任务列表或查看队列。 VS2015 中的“Parallel Stacks”或“Tasks”窗口是查看所有任务的好工具,但有局限性:

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

【问题讨论】:

  • 您在什么上下文中运行代码?它是 WPF 应用程序还是 webb?你能在单元测试中重现错误吗?
  • 另外,我注意到您没有等待 MonitorAsync。这将吞噬所有异常。看看这个问题stackoverflow.com/questions/15522900/…
  • “简化并发问题”——但看起来它正在导致严重的调试问题。我建议如果您想要后台活动,请使用BackgroundWorker 或显式启动线程并开始管理您的并发问题,而不是尝试通过创建多个任务来伪造并发它们都在竞争访问 UI 线程。
  • @smoksnes - 这是一个 WPF 应用程序。如描述中所述,它绝对不会引发异常,并且这种情况很容易覆盖,而无需将调用者更改为阻塞(实际上,对于关键监视器来说,UnobservedTaskException 就足够了)。也就是说,我确实喜欢 ContinueWith() 模式
  • @denis 该方法返回一个任务。你只是忽略它。它会告诉你它是否完成,以及导致它失败的任何错误。

标签: c# .net asynchronous async-await


【解决方案1】:

如果你不等待任务并且从不调用Wait()或Result或GetAwaiter()或ContinueWith(),你怎么知道它在被垃圾收集之前被安排在任何地方?

是否改变这一行:

罢工>

MonitorAsync(); // starts the Monitor

到这个

罢工>

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

让它开始运行?

编辑:这可能不是问题-according to MSDN documentation“当调用异步方法时,它会同步执行函数的主体,直到尚未完成的可等待实例上的第一个等待表达式,此时点调用返回给调用者。”,即无论您对Task 做什么,当然,直到第一个await 点或方法结束之前的一切都运行。我将此与创建Task 和new Task(...) 混为一谈,然后不在任务调度程序上调度它。 awaiting Task.Delay 内部应该足以注册该方法的其余部分作为延续,并确保它会被执行。

【讨论】:

  • 感谢您的评论。将 async 方法的主体移动到延续确实会导致相同的行为。问题似乎与调度程序有关。似乎在极端情况下,某些任务(但不是全部)实际上停止了分配时间。
  • 那么如果你捕获Task:var task = MonitorAsync();然后检查它的Status,那么任务之后会立即处于哪个状态?如果您启动计时器并定期检查它,它是否会进入任何其他状态?
  • 重新阅读这个问题,我想我的建议是离开 UI 上下文,因为那是拥挤的地方。从Task.Run() 操作内部启动MonitorAsync()(或使用ConfigureAwait(false))。如果您希望事情在没有并发的情况下连续运行,您可以创建一个ConcurrentExclusiveSchedulerPair 并使用独占调度程序来确保一次运行的任务不超过一个。如果您确实需要接触 UI 工作,只需在 UI 上下文中安排该工作
  • 我同意离开 UI 上下文可以避免这个问题。 ConcurrentExclusiveSchedulerPair 听起来像是简化某些复杂任务之间的并发问题的好主意。谢谢。我仍然希望深入了解它是如何工作的,因为调度程序在落后时似乎不是很公平。但也许这就是问题的简单答案。
  • 关于这一点的最后一点 - 请注意,如果您 Task.Run 即使在排他调度程序上安排的任务中,它也会在线程池上运行,所以如果您不等待它,您可能会无意中导致您的任务开始工作,并一直运行到下一次迭代中。
【解决方案2】:

我认为只有两种选择:

  1. 该方法引发异常。您已经说过没有,但也许您可以通过在调试器下运行您的应用程序来仔细检查,同时为所有 CLR 异常启用“抛出时中断”?
  2. UI 线程太忙了,以至于当它应该恢复时,它根本没有时间运行您的方法。由于这也意味着 UI 将停止响应,而您并没有说它会停止响应,所以这对我来说听起来不太可能。

【讨论】:

  • (1) 确认它绝对没有抛出异常。 (2)如果用户界面“有点忙”,它是否可能大部分都可以工作,而只是停止运行一些任务?这就是为什么我问我如何调试它并获得所有任务的列表?
  • @DenisP 执行任务可能会延迟,但不会被跳过。
  • 这是困扰我关于 WPF 的事情之一。每隔一段时间,你似乎就会失去一些事件。例如,我实际上已经看到它会将击键输入到 TextBox 中。在旧的 win32 消息泵时代,这永远不会发生。所有关键事件都进入队列,您可能处理它们的速度很慢,但您不会错过任何一个。
  • 在原始帖子中添加了更大的评论。总之,我的理论是它有利于最近创建的任务,因此较旧的任务会被不公平地延迟。仍然不确定如何访问要确认的任务列表。
  • 我已经确认我认为停止运行的任务(异步方法)并没有停止运行。例如,我运行了同一个任务的 3 个实例,在重现的极端场景中,其中 2 个持续运行,但其中一个需要 2 秒到 20 秒才能从“等待 Task.Delay(100)”返回。因此,边缘场景中任务的延迟似乎甚至不公平。
猜你喜欢
  • 1970-01-01
  • 2019-01-21
  • 1970-01-01
  • 2015-07-30
  • 2015-10-18
  • 1970-01-01
  • 1970-01-01
  • 2015-02-27
  • 2011-09-03
相关资源
最近更新 更多