我编写了一个多线程应用程序,它广泛使用了异步/等待。它应该在预定的时间下载一些东西。为此,它使用“await Task.Delay”。有时它每分钟发送数千个请求。
它按预期工作,但有时我的程序需要记录一些大的东西。当它这样做时,它会序列化许多对象并将它们保存到一个文件中。在那段时间里,我注意到我的计划任务执行得太晚了。我已将所有日志记录放到优先级最低的单独线程中,问题不再经常发生,但它仍然会发生。事情是,我想知道它什么时候发生,以便知道我必须使用类似的东西:
var delayTestDate = DateTime.Now;
await Task.Delay(5000);
if((DateTime.Now - delayTestDate).TotalMilliseconds > 6000/*delays up to 1 second are tolerated*/) Console.WriteLine("The task has been delayed!");
此外,我发现我也使用的“Task.Run”也会导致延迟。为了监控它,我不得不使用更难看的代码:
var delayTestDate = DateTime.Now;
await Task.Run(() =>
{
if((DateTime.Now - delayTestDate).TotalMilliseconds > 1000/*delays up to 1 second are tolerated*/) Console.WriteLine("The task has been delayed!");
//do some stuff
delayTestDate = DateTime.Now;
});
if((DateTime.Now - delayTestDate).TotalMilliseconds > 1000/*delays up to 1 second are tolerated*/) Console.WriteLine("The task has been delayed!");
我必须在每个 await 和 Task.Run 之前和之后以及在每个异步函数中使用它,这既丑陋又不方便。我不能将它放入一个单独的函数中,因为它必须是异步的,而且无论如何我都必须等待它。有人知道更优雅的解决方案吗?
编辑:
我在评论中提供的一些信息:
正如@YuvalItzchakov 所注意到的,问题可能是由线程池饥饿引起的。这就是为什么我使用 System.Threading.Thread
来处理线程池之外的日志记录,但正如我所说,问题有时仍然会发生。
我有一个四核处理器,通过从 ThreadPool.GetMaxThreads
中减去 ThreadPool.GetAvailableThreads
的结果,我得到 0 个繁忙的工作线程和 1-2 个繁忙的完成端口线程. Process.GetCurrentProcess().Threads.Count
通常返回大约 30。这是一个 Windows 窗体应用程序,虽然它只有一个带菜单的托盘图标,但它从 11 个线程开始。当它达到每分钟发送数千个请求时,它很快就会增加到 30 个。
正如@Noseratio 所建议的,我尝试使用 ThreadPool.SetMinThreads
和 ThreadPool.SetMaxThreads
,但它甚至没有改变上面提到的繁忙线程的数量。
最佳答案
当您执行 Task.Run 时,它会使用线程池线程来执行这些任务。当您有长时间运行的任务时,您会导致线程池饥饿,因为它的资源当前被长时间运行的任务占用。
2条建议:
运行长时间运行的任务时,确保将 Task.Factory.Startnew 与 TaskCreationOptions.LongRunning 结合使用,这将触发新线程的创建。您在这里也必须小心,因为旋转太多新线程会导致过多的上下文切换,从而导致您的应用速度变慢
在必须执行 IO Bound 工作的地方使用真正的异步,使用支持 TAP 的 api,例如 HttpClient 和 Stream,这不会导致新线程执行阻塞工作。
关于c# - await Task.Delay 花费的时间比预期的要长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23171696/