为什么我的计数器变量在多线程场景中没有返回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();
                }
            }

为什么我的计数器变量在多线程场景中没有返回0 ?

是的,在某些情况下finally块可能永远不会运行。

  • 您可以在最后执行之前关闭计算机。
  • try块中的代码可能永远不会终止(例如无限循环)。
  • Environment.FailFast没有运行finally block .
  • 运行时的严重错误可能会导致整个进程崩溃而不执行finally。

此外,如果finally块被中断,抛出异常或进入无限循环,则它可能开始运行但未完成。


这里虽然你的问题似乎是你正在使用多个线程,但不同步访问共享变量:

numberOfRunningThreads++;

你的问题是你没有锁定numberOfRunningThreads的访问权限。
多个线程可以将此修改为一次导致竞争条件,其中numberOfRunningThreads没有正确地递增或递减。

可以用Interlocked.IncrementInterlocked.Decrement代替++..

根据OP的要求(见上面的评论),关于更新的问题/代码:

lock语句的行为就像一个"关键"段,即它阻止了特定代码块的并行执行——lock的参数被用作"同步对象",这样在同一变量上被lock包围的任何代码块都不会在不同的线程上并行执行。

锁语句不会对变量/它的内容做任何事情——例如,它不会"冻结"内容,所以它们仍然可以在任何执行代码(并行)的任何地方随时更改,只要该代码不受lock的保护,并且该变量与参数相同。

如果你看一下我提供的链接(你指向的是VS2003的一个过时的文档版本),它明确地说,例如锁(this)是不可以使用的-原因和最佳实践相应描述…

以上所有内容使得所提供的代码(更新版本)仍然不是线程安全的(尽管肯定比原始版本更好)。