为什么我的计数器变量在多线程场景中不会回到0?

Giu*_*olo 0 c# windows-services timer try-catch-finally

我正在构建一个Windows服务基类来管理轮询任何待处理任务的计划表并运行它们.

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;
    }
Run Code Online (Sandbox Code Playgroud)

计时器调用的委托方法保留正在运行的线程的计数,以便可以在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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

通常,当我尝试从Windows服务管理控制台停止服务时,服务不会停止.如果我调试它并向OnStop()方法添加断点,我可以看到它不是因为numberOfRunningThreads卡在大于0的数字上(通常远大于10!).没有任务正在运行,它永远保留在该号码上!

首先,我不明白这个数字怎么可能大于10,尽管它ThreadPool.SetMaxThreads应该限制​​在10?

其次,即使我没有设置最大线程数,我也期望PollWrapper的finally块最终将计数恢复为0.如果计数器保持大于0,则只能用finally块来解释执行,对吧!?怎么可能?

最后,你会建议一种不同的方式将Poll限制为一些可能的并发运行线程到固定数量(.NET 3.5)吗?

非常感谢.

更新:

在阅读了Yahia关于reentrancy和SetMaxThread的评论后,我修改了PollWrapper,以便始终限制生成的运行线程的最大数量.我仍然要确保民意调查是可以重入的.

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();
                }
            }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 7

是的,有些情况下finally块可能永远不会运行.

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

此外,finally块可能会开始运行但如果它被中断则不完整,如果它抛出异常,或者它进入无限循环.


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

numberOfRunningThreads++;
Run Code Online (Sandbox Code Playgroud)

您需要在此处锁定共享对象.

  • @GiuseppeR,基于我对解决方案的猜测,你可以将其改写为"为什么我的计数器变量在多线程场景中不会回到0?". (4认同)

Alb*_*nbo 5

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

您可以使用Interlocked.Increment,Interlocked.Decrement而不是++...

  • @GiuseppeR int read和int write是一个原子操作,但i ++是一个读操作,一个计算操作和一个写操作.如果我在计算操作期间改变了东西就去香蕉吧. (3认同)