System.Threading.Timer

时间:2017-11-09 09:24:28

标签: c# asynchronous timer

简介: 我有一个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

2 个答案:

答案 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
}