C#System.Timers.Timer奇怪的行为?

时间:2018-11-07 11:40:14

标签: c# concurrency locking system.timers.timer

我的目标是编写一个代码段,使我可以在并发环境中对对象(例如txt文件)进行独占访问。考虑到这一点,我正在测试使用两个System.Timers计时器构建的简单程序。两个计时器的事件处理程序共享相同的锁定对象(请参见下面的代码)。
定时器以不同的间隔同时启动,定时器1为3秒,定时器2为1秒。 Timer1应该只工作一个周期,在此期间它的事件处理程序将休眠10秒,从而保持锁定状态。
令我感到惊讶的是,当锁释放时,我没有将所有事件堆积在内存中的timer2事件中(仅应用程序。我以为,虽然timer1的事件处理程序具有锁定,但timer2的事件仍在内存中堆积。但这显然是不正确的。为什么某些Timer2事件消失了?

class Program
{
    static int counter = 0;
    static readonly object locker = new object();
    System.Timers.Timer timer1;
    System.Timers.Timer timer2;

    static void Main(string[] args)
    {
        Program p = new Program();

        p.timer1 = new System.Timers.Timer(3000);
        p.timer1.Elapsed += new ElapsedEventHandler(p.Timer1EventHandler);
        p.timer1.Start();
        p.timer2 = new System.Timers.Timer(1000);
        p.timer2.Elapsed += new ElapsedEventHandler(p.Timer2EventHandler);
        p.timer2.Start();
        ThreadPool.SetMaxThreads(50, 50);
        Console.ReadLine();
    }

    void Timer1EventHandler(object sender, ElapsedEventArgs e)
    {
        timer1.Stop();
        DoThingsForTimer1Event();
    }

    void DoThingsForTimer1Event()
    {
        lock (locker)
        {
            Console.WriteLine(DateTime.Now + " Timer1 event started." + " Current thread number " + Thread.CurrentThread.ManagedThreadId);

            Thread.Sleep(10000);

            Console.WriteLine(DateTime.Now + " Timer1 event finished. Lock released.");
        }

    }

    void Timer2EventHandler(object sender, ElapsedEventArgs e)
    {
        counter++;
        lock (locker)
        {
            Console.WriteLine(DateTime.Now + " Timer2 event fired. Current thread number " + Thread.CurrentThread.ManagedThreadId +
                " Counter=" + counter);
        }                                         
    }
}

enter image description here

1 个答案:

答案 0 :(得分:2)

感谢@TheGeneral将其识别为OP的根本原因。

您遇到的主要问题是ThreadPool已用尽(并且Timer正在使用ThreadPool),因为您的CPU只有4个逻辑核。这就解释了为什么我个人(具有12个核心)无法重现此内容。

根据the docs

  
    

默认情况下,最小线程数设置为系统上的处理器数。

  

因此线程池调度程序可能从4个线程开始。线程池调度程序非常保守。它不仅会按您的要求分配线程,有时还会delays creating them来提高整体系统性能(因为分配线程很昂贵)。

要解决您的立即问题,您可以使用以下命令提示线程池更快地启动更多线程:

ThreadPool.SetMinThreads(50, 50);

这将使它迅速增加到50,然后在此之后更加保守。

从长远来看,问题在于您正在线程池中进行长时间运行的操作。这是一个坏主意。您可能希望将它们移至线程或long running tasks(实际上是线程)。但是,这两种选择都有其缺点。从根本上说,如果可能的话,您希望在线程池之外保留​​长时间运行的操作。

在不了解您为什么使用lock的情况下,很难给出很好的建议。但是要考虑的一个选项可能是使用BlockingCollection形成队列-然后使用单个单独的线程来处理该队列。这意味着您的Timer事件只会将一个条目添加到队列中,然后返回-处理的首要任务是在处理来自队列的条目的(单个)线程中。