简介: 我有一个Windows服务,如果它们在不同的时间间隔运行,它会监视其他应用程序和服务。 该服务为每个受监视的应用程序使用一个计时器(System.Threading.Timer)(命名为" monitor")。 不同类型的应用程序需要不同类型的监视器,一些同步工作而另一些同步(例如,使用HttpClient的那些)。
所以我到了需要在计时器中进行异步调用的地步。 我已将代码简化为限制,以便我可以在此处发布。它可以直接运行到控制台项目中。 我的问题是这个代码有一个非常奇怪的行为,因为引入了更多的定时器 - 它运行起来更难,直到它根本没有响应(超过20个定时器)。 监视器运行时间是否恰好是异步操作(100ms)中设置的延迟?
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace TestMain
{
class TestMain
{
private static List<TestTimer> timers = new List<TestTimer>();
static void Main(string[] args)
{
for (int i = 0; i < 20; i++)
{
TestMain.timers.Add(new TestTimer(i));
}
Console.WriteLine("Press [Enter] to exit.");
Console.ReadLine();
}
public class TestTimer
{
public Int32 Id { get; private set; }
private Timer timer;
public TestTimer(Int32 id)
{
this.Id = id;
this.timer = new Timer(this.Test, null, 1000, 30 * 1000);
}
private void Test(Object state)
{
TestWorker t = new TestWorker(this.Id);
t.Run();
}
}
public class TestWorker
{
public Int32 Id { get; private set; }
private Stopwatch sw = new Stopwatch();
public TestWorker(Int32 id) { this.Id = id; }
public void Run()
{
this.RunAsync().Wait();
}
private async Task RunAsync()
{
this.Log(String.Format("Start[{0,2}]", this.Id));
this.sw.Restart();
await Task.Run(() => { System.Threading.Thread.Sleep(100); }).ConfigureAwait(false);
this.sw.Stop();
this.Log(String.Format(" End[{0,2}] Duration=[{1}]", this.Id, (Int32)this.sw.ElapsedMilliseconds));
}
private void Log(String text)
{
Console.WriteLine(String.Format("{0,20} {1}", DateTime.Now, text));
}
}
}
}
我附上了一个带有跑步的印刷品。 Console Printscreen
答案 0 :(得分:1)
这是因为线程池管理其线程的方式。线程池具有“最小”线程数(您可以使用ThreadPool.GetMinThreads
读取)。默认情况下(这取决于.NET版本,但我们不会复杂的东西)它与处理器内核的数量有关,例如在我的机器上是8.当这8个线程忙,你需要更多 - 线程池将首先等待一段时间让其中一个忙线程可用(它会等待大约1秒钟),如果没有线程可用 - 它会再向池中添加一个线程。
计时器回调在线程池上执行。因此,当所有20个计时器同时触发它们的回调时 - 只执行8个(在我的情况下)回调。其余的排队,大约每秒执行一次(它们从线程池请求线程执行但每次等待1秒,因为线程池中的所有线程此刻都很忙)。他们很忙,因为您的计时器回调等待RunAsync
完成Wait()
。因此,只有在12(20-8)秒后,所有计时器回调都已执行。
执行计时器回调时 - 它会将Start
消息写入控制台,启动秒表。然后通过执行Task.Run
从线程池请求另一个线程。所有这些请求都在计时器回调后排队,因此只有在所有计时器启动后,您才会开始收到End
条消息。
现在你有20个线程忙于等待RunAsync
完成。第一个Task.Run
请求另一个线程。这个线程等待100毫秒,之后它就是空闲的并且可以重用,所以任务池不会为每个Task.Run
创建新的线程并将重用这个线程(因为100毫秒少于1秒它将等待一个线程变得可用。)
为了更加期望这种行为 - 将ThreadPool.SetMinThread
的线程池中的最小线程设置为更大的值,或者在等待RunAsync
完成时不保留计时器回调线程。
答案 1 :(得分:0)
System.Threading.Timer正在使用线程池,因此线程数量有限制,这就是您所遇到的。
监视器运行时间是否恰好是异步操作(100ms)中设置的延迟?
这就是你想要的,但似乎线程忙等待任务完成的持续时间甚至更多,因为里面的任务也想从线程中使用线程池。
快速解决方法是使用“即发即弃”方法(credits),这样计时器不会等待任何事情,而不是
public void Run()
{
RunAsync().Wait();
}
DO
public void Run()
{
#pragma warning disable 4014
RunAsync();
#pragma warning restore 4014
}