1

我的目标是编写一个代码片段,让我可以在并发环境中独占访问对象(f.ex 一个 txt 文件)。考虑到这一点,我正在测试使用两个 System.Timers 计时器构建的简单程序。两个计时器的事件处理程序共享相同的锁定对象(请参阅下面的代码)。
定时器以不同的时间间隔同时启动,定时器 1 为 3 秒,定时器 2 为 1 秒。Timer1 应该只工作一个周期,在此期间它的事件处理程序将休眠 10 秒,从而保持锁定。
令我惊讶的是,当锁被释放时,我并没有把所有的事件都堆放在内存中 timer2 事件(只有应用程序。每个其他事件)。我想,虽然 timer1 的事件处理程序有锁,但 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);
        }                                         
    }
}

在此处输入图像描述

4

1 回答 1

2

感谢@TheGeneral 将此确定为 OP 问题的根本原因。

您在这里遇到的主要问题是您ThreadPool已经筋疲力尽(并且您Timer正在使用ThreadPool),因为您的 CPU 只有 4 个逻辑核心。这解释了为什么我个人(有 12 个核心)无法重现这一点。

根据文档

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

所以线程池调度器很可能从 4 个线程开始。线程池调度程序相当保守。它不仅会根据您的要求提供线程 - 它有时会延迟创建它们以帮助提高整体系统性能(因为启动线程很昂贵)。

要解决您当前的问题,您可以提示线程池更快地启动更多线程,使用:

ThreadPool.SetMinThreads(50, 50);

这将使其迅速升至 50,然后更保守。

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

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

于 2018-11-07T12:46:46.930 回答