在C#中实现多线程(代码审查)

时间:2008-09-28 01:57:52

标签: c# multithreading

问候。

我正在尝试在应用程序中实现一些多线程代码。此代码的目的是验证数据库提供的项目。验证可能需要很长时间(几百毫秒到几秒),因此需要将此过程分解为每个项目的自己的线程。

数据库可能在开始时每秒给它20或30个项目,但开始迅速下降,最终在24小时内达到约65K项目,此时应用程序退出。

如果有更多知识渊博的人可以查看我的代码并查看是否存在任何明显问题,我会喜欢它。我工作过的任何人都不知道多线程,所以我真的只是靠自己一个人。

这是代码。它有点长但应该很清楚。如果您有任何反馈或建议,请告诉我。谢谢!

public class ItemValidationService
{
    /// <summary>
    /// The object to lock on in this class, for multithreading purposes.
    /// </summary>
    private static object locker = new object();

    /// <summary>Items that have been validated.</summary>
    private HashSet<int> validatedItems;

    /// <summary>Items that are currently being validated.</summary>
    private HashSet<int> validatingItems;

    /// <summary>Remove an item from the index if its links are bad.</summary>
    /// <param name="id">The ID of the item.</param>
    public void ValidateItem(int id)
    {
        lock (locker)
        {
            if
            (
                !this.validatedItems.Contains(id) &&
                !this.validatingItems.Contains(id)
            ){
                ThreadPool.QueueUserWorkItem(sender =>
                {
                    this.Validate(id);
                });
            }
        }

    } // method

    private void Validate(int itemId)
    {
        lock (locker)
        {
            this.validatingItems.Add(itemId);
        }

        // *********************************************
        // Time-consuming routine to validate an item...
        // *********************************************

        lock (locker)
        {
            this.validatingItems.Remove(itemId);
            this.validatedItems.Add(itemId);
        }

    } // method

} // class

8 个答案:

答案 0 :(得分:4)

如果您的轻量级零星处理不是时间敏感的,则线程池是一个方便的选择。但是,我记得在MSDN上读到它不适合这种性质的大规模处理。

我把它用于与此非常相似的东西并且后悔了。我在后续应用程序中采用了工作线程方法,并且对我的控制水平感到非常高兴。

我在工作线程模型中最喜欢的模式是创建一个包含任务项队列的主线程。然后派出一堆工作人员从该队列中弹出项目进行处理。我使用阻塞队列,这样当进程中没有项时,工作程序就会阻塞,直到某些东西被推入队列。在此模型中,主线程从某个源(db等)生成工作项,并且工作线程使用它们。

答案 1 :(得分:2)

我的第二个想法是使用阻塞队列和工作线程。这是我过去使用的阻塞队列实现,效果很好: http://www.codeproject.com/KB/recipes/boundedblockingqueue.aspx

验证逻辑涉及哪些内容?如果它主要是CPU绑定,那么我将在盒子上为每个处理器/核心创建不超过1个工作线程。这将告诉您处理器的数量:    Environment.ProcessorCount

如果您的验证涉及I / O,例如文件访问或数据库访问,那么您可以使用比处理器数量更多的线程。

答案 2 :(得分:1)

小心,QueueUserWorkItem might fail

答案 3 :(得分:1)

在问题中发布的代码中可能存在逻辑错误,具体取决于ValidateItem(int id)中的商品ID来自何处。为什么?因为尽管在排队工作项之前正确锁定了validatingItems和validatedItems队列,但在新线程旋转之前,不会将该项添加到validatingItems队列。这意味着可能存在时间间隔,其中另一个线程使用相同的id调用ValidateItem(id)(除非它在单个主线程上运行)。

我会在锁定内部排队项目之前将项目添加到validatingItems队列。

编辑:同时QueueUserWorkItem()返回一个bool,因此您应该使用返回值来确保该项目已排队,然后将其添加到validatingItems队列。

答案 4 :(得分:1)

ThreadPool可能不是最适合干扰它的那么多。您可能想要研究其功能的上限和/或自己动手。

此外,如果您不希望重复验证,则代码中存在竞争条件。致电

this.validatingItems.Add(itemId);

需要在主线程(ValidateItem)中发生,而不是在线程池线程(Validate方法)中发生。在将工作项排队到池之前,此调用应该在一行之前发生。

通过不检查QueueUserWorkItem的返回来发现更糟糕的错误。排队可能会失败,为什么它不会抛出异常对我们所有人来说都是一个谜。如果返回false,则需要删除添加到validatingItems列表中的项,并处理错误(可能抛出异常)。

答案 5 :(得分:0)

我会关注这里的表现。您表示数据库可能每秒为其提供20-30个项目,并且一个项目可能需要几秒钟才能进行验证。这可能是相当多的线程 - 使用您的指标,最糟糕的情况是60-90个线程!我想你需要重新考虑这里的设计。迈克尔提到了一个不错的模队列的使用确实有助于控制和组织事物。也可以使用信号量来控制创建的线程数 - 也就是说,您可以拥有最大线程数,但在较小的负载下,如果完成工作的次数较少,则不一定要创建最大数量 - - 即你自己的游泳池大小可能是一个动态的上限。

使用线程池时,我发现在执行工作时监视池中线程的执行更加困难。因此,除非它是火和忘记,我赞成更多的控制执行。我知道您提到您的应用程序在65K项目全部完成后退出。你如何监视你的线程,以确定他们是否已经完成了他们的工作 - 即所有排队的工作人员都完成了。您是否正在监控HashSet中所有项目的状态?我认为通过排队你的项目并让你自己的工作线程消耗掉那个队列,你可以获得更多的控制权。尽管如此,这可能会以线程之间的信号传输开销为代价,以指示所有项目何时排队等待允许它们退出。

答案 6 :(得分:0)

您也可以尝试使用CCR - 并发和协调运行时。它隐藏在Microsoft Robotics Studio中,但为执行此类操作提供了出色的API。

你只需要创建一个“端口”(本质上是一个队列),连接一个接收器(当一些东西被发布到它时被调用的方法),然后将工作项发布到它。 CCR处理队列和工作线程以运行它。

Here's a video on Channel9 about the CCR.

这是非常高性能的,甚至被用于非机器人的东西(Myspace.com在场景后面使用它作为他们的内容传送网络)。

答案 7 :(得分:0)

我建议您调查MSDN: Task Parallel Library - DataFlow。您可以找到实施for loop .... .... call mydatetimepicker(variable, variable) mydatetimepicker(variable, variable) { code to create a datetimepicker } 的示例,例如要验证的数据库Producer-Consumer项,而验证例程将成为producing

还建议将consumer用作“并发”哈希集,在其中仅填充没有值的键:)。您可以将代码制作为ConcurrentDictionary<TKey, TValue>