我编写了一个多线程应用程序,它广泛使用async / 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.GetAvailableThreads
中减去ThreadPool.GetMaxThreads
的结果,我得到0个繁忙的工作线程和1-2个繁忙的完成端口线程。 Process.GetCurrentProcess().Threads.Count
通常返回大约30个。它是一个Windows窗体应用程序,虽然它只有一个带菜单的托盘图标,但它以11个线程开始。当它每分钟发送数千个请求时,它很快就会达到30个。
正如@Noseratio建议的那样,我尝试使用ThreadPool.SetMinThreads
和ThreadPool.SetMaxThreads
,但它甚至没有改变上面提到的忙线程数。
答案 0 :(得分:3)
执行Task.Run时,它使用线程池线程来执行这些任务。当你有长时间运行的任务时,你正在导致线程池的饥饿,因为它的资源当前被长时间运行的任务所占用。
2建议:
运行长时间运行的任务时,请确保将Task.Factory.Startnew与TaskCreationOptions.LongRunning一起使用,这将触发新的线程创建。你也必须谨慎,因为旋转过多的新线程会导致过多的上下文切换,导致你的应用程序变慢
使用true async,你必须做IO绑定工作,使用支持TAP的api,如HttpClient和Stream,这不会导致新线程执行阻塞工作。
答案 1 :(得分:2)
async / await中存在开销,以及以较低优先级执行的任务本身。如果您需要以准确的时间间隔可靠地发生某些事情,则async / await / TPL不是要使用的接口。
尝试创建一个循环的独立后台线程,直到它被安排工作。这样,您可以直接控制优先级和时间,而无需通过TPL / async。
Thread backgroundThread = new Thread(BackgroundWork);
DateTime nextInterval = DateTime.Now;
public void BackgroundWork()
{
if(DateTime.Now > nextInterval){
DoWork();
nextInterval = nextInterval.Add(new TimeSpan(0,0,0,10)); // 10 seconds
}
Thread.Sleep(100);
}
根据需要调整睡眠(..)和间隔值。
答案 2 :(得分:1)
我认为您正在体验Joe Duffy在"CLR thread pool injection, stuttering problems"博文中所描述的情况:
我们的线程池目前所做的一件愚蠢的事情与它如何有关 创建新线程。也就是说,它严重限制了新的创造 线程一旦超过“最小”线程数,通过 default,是计算机上的CPU数。我们限制自己 一旦达到或超过这个数字,每500ms最多一个新线程。
一种解决方案可能是在使用TPL之前显式增加线程池线程的最小数量,例如:
ThreadPool.SetMaxThreads(workerThreads: 200, completionPortThreads: 200);
ThreadPool.SetMinThreads(workerThreads: 100, completionPortThreads: 100);
尝试使用这些数字,看看问题是否消失。