C# System.Timers.Timer 奇怪的行为?
C# System.Timers.Timer odd behavior?
我的目标是编写一个代码片段,让我可以在并发环境中独占访问一个对象(f.ex 一个 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);
}
}
}
感谢@TheGeneral 将此确定为 OP 问题的根本原因。
你 运行 进入这里的主要问题是你的 ThreadPool
已经用完了(你的 Timer
正在使用 ThreadPool
),因为你有一个 CPU只有4个逻辑核心。这解释了为什么我个人(12 核)无法重现这一点。
根据the docs:
By default, the minimum number of threads is set to the number of processors on a system.
所以线程池调度程序很可能从 4 个线程开始。线程池调度器非常保守。它不只是按照您的要求分配线程 - 它有时 delays creating them 有助于提高整体系统性能(因为启动线程很昂贵)。
要解决您的即时问题,您可以提示线程池更快地启动更多线程,使用:
ThreadPool.SetMinThreads(50, 50);
这会使其迅速升至 50,之后会更加保守。
从长远来看,问题是您在线程池中进行了长时间的 运行 操作。这是一个坏主意。您可能希望将它们移动到线程或 (实际上是线程)。但这两种选择都有其缺点。从根本上说,如果可能,您希望在线程池之外保留长时间的 运行 操作。
如果不理解您使用 lock
的原因,很难给出很好的建议。但是要考虑的一个选择可能是使用 BlockingCollection
来形成一个队列 - 然后让一个单独的线程处理该队列。这意味着您的 Timer
事件只会向队列添加一个条目,然后 return - 处理的首当其冲将在处理队列条目的(单个)线程中。
我的目标是编写一个代码片段,让我可以在并发环境中独占访问一个对象(f.ex 一个 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);
}
}
}
感谢@TheGeneral 将此确定为 OP 问题的根本原因。
你 运行 进入这里的主要问题是你的 ThreadPool
已经用完了(你的 Timer
正在使用 ThreadPool
),因为你有一个 CPU只有4个逻辑核心。这解释了为什么我个人(12 核)无法重现这一点。
根据the docs:
By default, the minimum number of threads is set to the number of processors on a system.
所以线程池调度程序很可能从 4 个线程开始。线程池调度器非常保守。它不只是按照您的要求分配线程 - 它有时 delays creating them 有助于提高整体系统性能(因为启动线程很昂贵)。
要解决您的即时问题,您可以提示线程池更快地启动更多线程,使用:
ThreadPool.SetMinThreads(50, 50);
这会使其迅速升至 50,之后会更加保守。
从长远来看,问题是您在线程池中进行了长时间的 运行 操作。这是一个坏主意。您可能希望将它们移动到线程或
如果不理解您使用 lock
的原因,很难给出很好的建议。但是要考虑的一个选择可能是使用 BlockingCollection
来形成一个队列 - 然后让一个单独的线程处理该队列。这意味着您的 Timer
事件只会向队列添加一个条目,然后 return - 处理的首当其冲将在处理队列条目的(单个)线程中。