0

我正在构建一个 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;
    }

计时器调用的委托方法保持运行线程的计数,以便可以在 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 块来解释执行,对吧!?这怎么可能?

最后,您是否会建议一种不同的方法来将轮询限制为固定数量的可能并发运行线程(.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();
                }
            }
4

3 回答 3

7

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

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

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


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

numberOfRunningThreads++;

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

于 2011-09-29T13:34:08.240 回答
5

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

您可以使用Interlocked.Increment,Interlocked.Decrement代替++and ..

于 2011-09-29T13:36:35.753 回答
0

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

lock语句的行为类似于“关键”部分,即它阻止该特定代码块并行执行 - 参数lock用作“同步对象”,因此lock不会执行同一变量上由 a 包围的任何代码块在不同的线程上并行。

lock 语句不对变量/它的内容做任何事情 - 例如它不会“冻结”内容,因此只要该代码不受保护,它们仍然可以在任何执行代码(并行)中随时随地更改lock和参数相同的变量。

如果您查看我提供的链接(您指向 VS2003 的过时文档版本),它明确表示例如 lock(this) 不能使用 - 原因和最佳实践已相应描述...

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

于 2011-10-03T16:46:05.880 回答