Windows服务定期生成线程,最多不超过指定数量

3

我已经阅读了数百页并使用了很多示例,但现在我完全困惑了。大多数示例似乎针对以下内容:

  • 设置计时器生成一个新线程来完成一些工作,线程数量无限
  • 生成特定数量的线程,每个线程都有一个计时器执行某项任务
  • 定期执行一些工作

我要实现的是:

  • 设置计时器以定期生成线程
  • 这个线程可能会比计时器滴答声所需的时间更长
  • 设定生成线程的数量上限
  • 当线程完成后将其返回,以便可以重新使用它
  • 每个线程中的工作互不干扰,可以异步运行(这并不重要)

类比一下,我希望我的应用程序像一艘拥有10根鱼线的渔船一样工作。一开始你会投掷一条鱼线(按需求),然后再投掷另一条,以此类推。在任何时候,水中可能有0到10条鱼线。每当捕到一条鱼时,就会拉起鱼线,并准备好再次投掷(如果需要的话)。

听起来我应该使用线程池?我还在考虑,为了保持简单,是否应该生成一个线程,如果没有工作,则立即返回它(例如从某个表中选择计数,如果计数为0,则返回线程),而不是尝试智能地确定是否需要生成线程。

一般来说,当系统处于空闲状态时,它将不断生成一个线程,然后查看是否有工作可做,如果没有则返回,但在繁忙时期,线程可能会被用到极限,直到其中一个或多个完成时才会返回。

我已经尝试过System.Threading和Semaphores以及WaitHandles,但这些都变得非常混乱。我还没有代码可以展示,因为我一直在删除并采用不同的方法。任何帮助都将不胜感激。

我使用C#2010进行开发。

3个回答

2

我之前使用线程池和定时器已经实现过类似的功能:

public class MaxThreadCountWorker : IDisposable
{
    private readonly int _maxThreadCount;
    private readonly int _tickIntervalMilliseconds;
    private readonly Action _periodicWork;
    private readonly System.Threading.Timer _timer;
    private int _currentActiveCount;

    public MaxThreadCountWorker(int maxThreadCount, int tickIntervalMilliseconds, Action periodicWork)
    {
        _maxThreadCount = maxThreadCount;
        _tickIntervalMilliseconds = tickIntervalMilliseconds;
        _periodicWork = periodicWork;
        _timer = new System.Threading.Timer(_ => OnTick(), null, Timeout.Infinite, Timeout.Infinite);
        _currentActiveCount = 0;
    }

    public void Start()
    {
        _timer.Change(0, _tickIntervalMilliseconds);
    }

    public void Stop()
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);
    }

    private void DoWork()
    {
        try
        {
            _periodicWork.Invoke();
        }
        finally
        {
            Interlocked.Decrement(ref _currentActiveCount);
        }
    }
    private void OnTick()
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);
        try
        {
            if (_currentActiveCount >= _maxThreadCount) return;

            Interlocked.Increment(ref _currentActiveCount);
            ThreadPool.QueueUserWorkItem(_ => DoWork());
        }
        finally
        {
            _timer.Change(_tickIntervalMilliseconds, _tickIntervalMilliseconds);
        }
    }

    public void Dispose()
    {
        _timer.Dispose();
    }
} 

class Program
{
    private static object _lockObject = new object();
    private static int _runningTasks = 0;

    private static void PrintToConsole()
    {
        lock (_lockObject)
        {
            _runningTasks += 1;
        }
        try
        {
            Console.WriteLine("Starting work. Total active: {0}", _runningTasks);
            var r = new Random();
            System.Threading.Thread.Sleep(r.Next(3500));                
        } finally
        {
            lock (_lockObject)
            {
                _runningTasks -= 1;
            }                
        }
    }
    static void Main(string[] args)
    {
        using (var w = new MaxThreadCountWorker(3, 150, PrintToConsole))
        {
            w.Start();
            Console.ReadKey();
        }
    }
}

太棒了!我觉得这实际上正在运行!我会进行更多的初步测试,因为唯一的方法是加载系统(或减少负载),看看线程使用情况是否增加或减少,但到目前为止,它似乎在工作 :-) - Adriaan Pretorius
阿伦,你的代码非常准确,而且完美运作!如果你希望我在我的代码中署名表彰你,请告诉我,我很乐意在那里用“铜牌”的方式展示你的名字,让所有(开发者)都看到! - Adriaan Pretorius

0

我认为你应该使用内置的ThreadPool而不是自己发明一个。你在问题中描述的情况是实现线程池所需处理的复杂性的一部分。我认为,除非你是为了学习线程池的工作原理,否则使用现有的线程池可以获得很多好处。

要使用内置的线程池,你应该在定时器tick的事件处理程序中调用TreadPool.QueueUserWorkItem()


谢谢Anders,我的最后一次尝试实际上是使用ThreadPool,但事情发生得太快了,我不能确定它是否真正利用了多个线程,而睡眠线程并不能告诉我它是否正在工作,这就是为什么我决定向世界求助。这似乎是一个非常简单的概念,但它却让我无法理解。 - Adriaan Pretorius

0

线程池是你的答案。真正的问题是你是否需要从线程中获得任何反馈。如果不需要,就像现在这种情况一样,在线程池中启动线程,它将管理线程数量的限制。然后继续处理计时器滴答声,作为一个睡眠进程。

如果你需要任何返回值,那么处理过程会变得更加复杂。

是的,启动线程,让它决定是否有工作要做。在这种情况下,这是完全合理的,没有必要检查是否需要启动任何东西。


这种方法的示例似乎总是以整个应用程序结束(使用某些ManualResetEvent)为结尾,但我宁愿将线程返回到池中。 我想我的问题(如果无知或听起来很蠢,我很抱歉!)是如何将线程返回到池中? - Adriaan Pretorius
我正在查看MSDN资源中的示例3(http://msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx),它似乎使用了您所解释的内容,以及Anders Abel关于QueueUserWorkItem的说法。该示例在达到限制后似乎会结束。如何将线程返回,以便池可以永远运行? - Adriaan Pretorius
据我所知,当您完成一个线程时,它将返回到线程池中。因此,只要线程池的上下文仍在运行,您就可以通过启动更多的工作项来重复使用这些线程。 - Schroedingers Cat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接