【问题标题】:Task gets stuck in "[Scheduled and Waiting to Run]"任务卡在“[计划并等待运行]”中
【发布时间】:2021-01-06 18:40:55
【问题描述】:

我遇到了一个我似乎无法弄清楚的任务问题。此应用程序通过 WebClient 对多个服务器进行重复的 HTTP 调用。它维护一个运行 HTTP 调用的任务字典,每五秒钟检查一次结果,然后一旦结果出现,它就会再次进行 HTTP 调用。这将在应用程序的整个生命周期内持续进行。

最近,它开始出现任务随机卡在 WaitingForActivation 中的问题。在调试器中,任务显示为“[计划并等待运行]”,但它从未运行。

这是它正在运行的函数,当我在调试器中单击“Scheduled”任务时,它指向 DownloadStringTaskAsync() 行:

private static async Task<string> DownloadString(string url)
{
    using (var client = new WebClient()) {
        try {
            var result = await client.DownloadStringTaskAsync(url).ConfigureAwait(false);
            return result;
        } catch (WebException) {
            return null;
        }
    }
}

实际创建运行上述函数的任务的代码是这样的。它仅在现有任务完成后才到达此行,Task.IsCompleted 永远不会返回 true,因为它处于计划状态。 Task.Status 卡在 WaitingForActivation 中。

tasks[resource] = Task.Run(() => DownloadString("http://" + resources[resource] + ":8181/busy"));

关于这件事的奇怪之处在于,据我所知,这段代码在两年内运行得非常好,直到我们最近进行了一次服务器迁移,其中包括升级的操作系统并引发了网络丢包问题。那是我们开始注意到这个特殊问题的时候,虽然我不知道这两者之间有什么关系。

此外,这往往仅在应用程序运行数千秒后才会发生。它在一段时间内运行得非常好,直到一个接一个的任务开始卡住。大约一天后,通常会有四五个任务卡在计划中。由于第一个任务卡住通常需要一些时间,所以在我看来会有某种竞争条件,但我不明白这是怎么回事。

任务会卡在计划中并且从未真正运行过吗?

【问题讨论】:

  • WaitingForActivation 不是“已计划并等待运行” - 您确定您正在查看相同的任务吗? WaitingForActivation 中的任务一段时间是完全正常的,无需担心。 WaitingToRun一段时间内的任务,通常表示线程池饱和。
  • 你想达到什么目的?了解为什么某些线程没有在您不需要线程的代码中启动,或者使软件工作?删除包装Task.Run 并再次检查。要回答最初的问题,需要更多信息,例如有多少任务同时工作。稍后会发生什么,在 Task 完成后(您是否将其从字典中删除?并发字典?),正在使用什么样的 TaskSceduler,环境是什么等。换句话说,只有可重现的示例才能帮助我找出最初的内容发生。顺便说一句,您对我的回答有任何反馈吗?
  • 还有一件事:WebClient 为每个请求创建新的 TCP 连接,这样你可能会用完套接字。然后答案中的HttpClient 可能会有所帮助,因为它可以重用打开的连接。试一试。
  • 至于我在寻找什么,我想了解为什么任务没有开始,所以我可以从中获得一些额外的知识,但我可以满足于获得应用程序好好工作。代码中有很多任务正在进行,大约有十几个显示在任何给定时刻计划/运行(其中大约一半与此代码相关)。任务完成后,在接下来的 5 秒循环中,它将开始一个新的循环 - 相同的 task[resource] 行将运行用新的任务替换已完成的任务。这是一个非并发字典。
  • 我没有使用任何任务调度程序 - 我只有很多 Task.Run() 和 async/await 乱七八糟。 (很确定其中很多是不必要的。)环境是 Windows 2016 服务器,项目针对框架 4.6.1,应用程序通常作为 Windows 服务运行,虽然我一直在调试它,但我一直在通过 Visual 运行它工作室。

标签: c# .net async-await


【解决方案1】:

我不熟悉古老的WebClient(可能包含错误),但可以建议微软推荐的使用System.Net.Http.HttpClient 从服务器获取响应的方法。此外,HttpClient 在每个端点处理多个请求时速度更快,尤其是在 .NET Core/.NET 5 中。

// HttpClient is intended to be instantiated once per application, rather than per-use
private static readonly HttpClient client = new HttpClient();

private static async Task<string> DownloadString(string url)
{
    try
    {
        return await client.GetStringAsync(url).ConfigureAwait(false);
    }
    catch (HttpRequestException ex)
    {
        Debug.WriteLine(ex.Message);
        return null;
    }
}

还要去掉Task.Run,这是一种冗余。

tasks[resource] = DownloadString($"http://{resources[resource]}:8181/busy");

Asynchronous programming - 阅读文章。您必须区分 I/O-bound 和 CPU-bound 工作,并且不要在没有特别需要并发的情况下生成线程。你需要no Thread这里。

【讨论】:

  • 我确实实施了您的建议。删除无关的 Task.Run() 已经删除了一个不必要的任务(调试器只显示两个相关的而不是三个),尽管这本身并不能解决问题。等待看看 HttpClient 是否有所作为。
  • @Nicholas did not fix the issue - 因此“任务”问题的根源在所示代码之外,可能在WebClient 内,不确定。
  • @Nicholas WaitingToRun 的original reason - 可能ThreadPool 已满。删除Task.Run() 应该可以部分解决问题。是的,HttpClient 可能会有所帮助,因为它更明智地使用线程。您可以限制活动作业的最大数量,例如使用 SemaphoreSlim 和一个循环来启动作业。
  • 使用 HttpClient 而不是 WebClient 解决了这个问题。我很好奇幕后发生了什么,但任务不再卡住了。
  • @Nicholas WebClient 太旧并且可能包含错误,例如回归类型,而 HttpClient 目前是官方推荐和支持的 API。预期的事情。
猜你喜欢
  • 2019-02-28
  • 2014-12-05
  • 2011-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
相关资源
最近更新 更多