为什么我的计数器变量在多线程场景中没有返回0 ?
本文关键字:返回 计数器 我的 变量 多线程 为什么 | 更新日期: 2023-09-27 18:10:06
我正在构建一个Windows Service基类来管理对任何未决任务的调度表的轮询和运行它们。
Windows服务正在使用System.Timers.Timer
启动调度表轮询。
在初始化计时器之前,我将ThreadPool.SetMaxThread
设置为10。
protected override void OnStart(string[] args)
{
ThreadPool.SetMaxThreads(10, 10);
this._Timer = new System.Timers.Timer();
this._Timer.Elapsed += new ElapsedEventHandler(PollWrapper);
this._Timer.Interval = 100;
this._Timer.Enabled = true;
}
计时器调用的委托方法保存正在运行的线程的计数,以便它可以在OnStop()方法中使用,等待每个线程在处置服务之前完成。
private void PollWrapper(object sender, ElapsedEventArgs e)
{
numberOfRunningThreads++;
try
{
this.Poll(sender, e);
}
catch (Exception exception)
{
//some error logging here
}
finally
{
numberOfRunningThreads--;
}
}
protected override void OnStop()
{
this._Timer.Enabled = false;
while (numberOfRunningThreads > 0)
{
this.RequestAdditionalTime(1000);
Thread.Sleep(1000);
}
}
通常,当我试图从Windows服务管理控制台停止服务时,服务不会停止。如果我调试它并在OnStop()方法中添加一个断点,我可以看到这并不是因为numberOfRunningThreads卡住在大于0的数字上(通常远远大于10!)。没有任务正在运行,它永远保持在这个数字上!
首先,我不明白这个数字怎么可能大于10,尽管ThreadPool.SetMaxThreads
应该限制为10?
其次,即使我没有设置最大线程数,我也希望PollWrapper的finally块最终将计数恢复为0。如果计数器一直大于0,它只能用finally块没有执行来解释,对吗?这怎么可能呢?
最后,您是否建议一种不同的方法将Poll限制为可能并发运行的线程的数量(固定数量)。NET 3.5) ?
许多谢谢。更新:
在阅读Yahia关于重入和SetMaxThread的评论后,我修改了PollWrapper,以便它应该总是限制生成的运行线程的最大数量。我仍然要确保Poll是可重入的。
private void PollWrapper(object sender, ElapsedEventArgs e)
{
lock(this)
{
if(this.numberOfRunningThreads < this.numberOfAllowedThreads)
{
this.numberOfRunningThreads++;
Thread t = new Thread(
() =>
{
try
{
this.Poll(sender, e);
}
catch (Exception ex)
{
//log exception
}
finally
{
Interlocked.Decrement(ref this.numberOfRunningThreads);
}
}
);
t.Start();
}
}
是的,在某些情况下finally块可能永远不会运行。
- 您可以在最后执行之前关闭计算机。
- try块中的代码可能永远不会终止(例如无限循环)。
-
Environment.FailFast
没有运行finally block . - 运行时的严重错误可能会导致整个进程崩溃而不执行finally。
此外,如果finally块被中断,抛出异常或进入无限循环,则它可能开始运行但未完成。
这里虽然你的问题似乎是你正在使用多个线程,但不同步访问共享变量:
numberOfRunningThreads++;
你的问题是你没有锁定numberOfRunningThreads的访问权限。
多个线程可以将此修改为一次导致竞争条件,其中numberOfRunningThreads没有正确地递增或递减。
可以用Interlocked.Increment
、Interlocked.Decrement
代替++
、..
根据OP的要求(见上面的评论),关于更新的问题/代码:
lock
语句的行为就像一个"关键"段,即它阻止了特定代码块的并行执行——lock
的参数被用作"同步对象",这样在同一变量上被lock
包围的任何代码块都不会在不同的线程上并行执行。
锁语句不会对变量/它的内容做任何事情——例如,它不会"冻结"内容,所以它们仍然可以在任何执行代码(并行)的任何地方随时更改,只要该代码不受lock
的保护,并且该变量与参数相同。
如果你看一下我提供的链接(你指向的是VS2003的一个过时的文档版本),它明确地说,例如锁(this)是不可以使用的-原因和最佳实践相应描述…
以上所有内容使得所提供的代码(更新版本)仍然不是线程安全的(尽管肯定比原始版本更好)。