等待Task.Delay花费的时间比预期的要长

hum*_*sta 6 .net c# asynchronous task-parallel-library async-await

我写了一个多线程应用程序,它广泛使用async/await.它应该在预定的时间下载一些东西.为此,它使用'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!");
Run Code Online (Sandbox Code Playgroud)

而且,我发现我也使用的'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!");
Run Code Online (Sandbox Code Playgroud)

我必须在每次await和Task.Run之前和之后以及每个异步函数内部使用它,这是丑陋和不方便的.我不能把它放到一个单独的函数中,因为它必须是异步的,无论如何我都要等待它.有没有人想到更优雅的解决方案?

编辑:

我在评论中提供的一些信息:

正如@YuvalItzchakov所注意到的,问题可能是线程池饥饿造成的.这就是为什么我过去常常System.Threading.Thread处理线程池之外的日志记录,但正如我所说,问题仍然有时会发生.

我有四个核心和减去的结果处理器ThreadPool.GetAvailableThreadsThreadPool.GetMaxThreads我得到0繁忙的工作线程和1-2繁忙完成端口线程.Process.GetCurrentProcess().Threads.Count通常返回大约30.这是一个Windows窗体应用程序,虽然它只有一个带菜单的托盘图标,但它以11个线程开始.当它每分钟发送数千个请求时,它很快就会达到30个.

正如@Noseratio建议的那样,我尝试使用ThreadPool.SetMinThreadsThreadPool.SetMaxThreads,但它甚至没有改变上面提到的繁忙线程的数量.

Yuv*_*kov 3

当您执行 Task.Run 时,它使用线程池线程来执行这些任务。当您有长时间运行的任务时,就会导致线程池饥饿,因为其资源当前被长时间运行的任务占用。

2 建议:

  1. 运行长时间运行的任务时,请确保将 Task.Factory.Startnew 与 TaskCreationOptions.LongRunning 一起使用,这将触发新线程的创建。您在这里也必须小心,因为旋转太多新线程将导致过多的上下文切换,从而导致您的应用程序变慢

  2. 在必须执行 IO 绑定工作的地方使用真正的异步,使用支持 TAP 的 api,例如 HttpClient 和 Stream,这不会导致新线程执行阻塞工作。