使用多线程循环

8
我对线程编程不熟悉,想实现类似于这个问题的功能:使用C#多线程加速循环(问题)。但是,我不确定那个解决方案是否适合我,因为我希望它们可以持续运行而不是停止。(另外,我使用的是.NET 3.5而不是2.0,与那个问题不同。)我想要实现以下功能:
foreach (Agent agent in AgentList)
{
    // I want to start a new thread for each of these
    agent.DoProcessLoop();
}

---

public void DoProcessLoop()
{
    while (true)
    {
        // do the processing

        // this is things like check folder for new files, update database
        // if new files found
    }
}

一个线程池是否是最佳解决方案,还是有更适合的东西?
更新:感谢所有出色的答案!我想详细解释一下用例。许多代理可以上传文件到一个文件夹中。每个代理都有自己的文件夹,可以将资产(csv文件、图像、pdf)上传到其中。我们的服务(它应该是在他们上传资产的服务器上运行的Windows服务,放心,我很快就会回来问一些关于Windows服务的问题 :))将不断检查每个代理的文件夹是否有新的资产,如果有,数据库将被更新,并为其中一些静态HTML页面创建。由于他们可能需要花费一些时间来上传所有内容,我们希望他们能够几乎立即看到他们上传的更改,因此认为每个代理一个线程是一个好主意,因为没有代理需要等待其他人完成(而且我们有多个处理器,希望充分利用它们的能力)。希望这解释得清楚!
谢谢,
Annelie

有点跑题,但是你可能想尝试使线程更加事件驱动,而不是在紧密循环中运行。那样的紧密循环很可能会让你的处理器因此繁忙浪费资源,也让那些真正需要工作的线程长时间闲置。 - Kitsune
更多的线程并不一定是答案,反而可能会成为问题。比如说你有20个代理,为每个代理生成一个线程。文件I/O始终会成为瓶颈,因此你有20个线程不断轮询文件系统以检测变化——这将对性能产生负面影响。每个线程还将使用CPU时间进行轮询,在四核盒子上,每个核心将受到五个这些线程的影响(假设有20个代理),因此正在执行实际工作(处理新文件)的线程将被交换出来,以允许轮询执行。 - Ragoczy
7个回答

12

鉴于您所描述的具体用法(监视文件),建议您使用FileSystemWatcher来确定何时有新文件,然后使用线程池中的线程处理这些文件,直到没有更多可处理的文件为止--此时该线程退出。

这应该会减少i/o(因为您不会不断轮询磁盘),降低CPU使用率(因为多个线程不断轮询磁盘会使用循环),并减少同时运行的线程数(假设未对文件系统进行不断修改)。

如果可能的话,您可能希望只在主线程上打开和读取文件,并将数据传递给工作线程,以将i/o限制为单个线程。


我正准备自己发布这个。 - Daniel Schaffer
这是正确的方法。您的FileSystemWatcher事件处理程序应该将新文件发布到队列中,由单独的监督线程监视,该线程生成单独的工作线程以摄取文件。这使您可以控制工作线程的数量并最小化FileSystemWatcher缓冲区溢出的机会。每个工作线程应不断测试,直到获得对其文件的独占访问权限,以便在文件的编写者完成之前不开始读取。 - Ed Power

5

2

使用线程池的一个问题是,如果线程池的大小比您想要的代理数量小,那么您尝试稍后启动的代理可能永远不会执行。一些任务可能永远不会开始执行,您可能会使使用线程池的应用程序域中的所有其他内容饿死。最好不要走这条路。


@tehMick 有趣的观点!你有什么其他建议吗?我们想要使用线程的原因是,我们不希望上传文件的人等待太久,而且由于我们有多个处理器,我们可以充分利用它们的能力。 - annelie
如果您正在监视文件系统,我一定会建议您使用单个线程来进行操作,因为多个线程会争夺文件访问权。然后,您可以将找到的每个文件排队作为工作项,供线程池挑选。但是,还有其他有效的方法来处理它。 - Eric Mickelsen
虽然我再想想,可能我对这里的用例有误解,我并不完全清楚上下文。 - Eric Mickelsen

2
你绝对不想使用线程池来完成这个任务。线程池线程不应该用于长时间运行的任务(“无限”被视为长时间运行),因为这显然会占用本应共享的资源。
对于你的应用程序,最好创建一个线程(而不是来自线程池),在该线程中执行你的while循环,在其中迭代你的代理集合并为每个代理执行处理。在while循环中,你还应该使用Thread.Sleep调用,以便不会使处理器达到极限(虽然有更好的周期性执行代码的方法,但对于你的目的来说,Thread.Sleep将起作用)。
最后,你需要包含某种方式使while循环在程序终止时退出。 更新:最后,多线程不会自动加速运行缓慢的代码。九个女人不能在一个月内生一个孩子。

1
但是八爪鱼妈妈每个月只能生一个孩子... :-P - Eric Mickelsen
1
虽然线程通常可以加速处理时间,尤其是在现代桌面和服务器机器的许多核心处理器中。只要没有单个资源需要每个线程,它们即使在单核机器上也会更快地执行。此外,线程池旨在被使用!我真的不认为它们意味着除了伪代码之外的 while(true)。 - Nate Zaugg
@Nate:OQ说:“我希望它们保持运行,永远不要结束”,我理解为他们字面上的意思是 while (true)。当然,线程池是可以使用的 - 只是不能这样使用。 - MusiGenesis

1
线程池在您预计线程会相对频繁地进入和退出,而不是针对预定义的一组线程时非常有用。

@Tejs 是的,我理解你的观点。有什么其他建议吗?(请查看我的先前评论以了解我们为什么选择这个方案) - annelie
我从不会实现 while(true) 循环 - 那是无法终止的,除非你想抛出异常或者干脆杀死线程。如果你需要并发,请考虑使用以下方法:<pre> Thread x = new Thread(new ThreadStart(Foo)); x.Start(); // 如果后来某些条件改变并且你想要停止它,请调用 x.Abort() public void Foo() { // 做一些事情 } </pre> - Tejs

0

嗯...正如Ragoczy所指出的那样,最好使用FileSystemWatcher来监视文件。然而,由于您有其他操作,您可以考虑使用多线程。

但是要注意,无论您有多少个处理器,它的容量都是有限的。您可能不想创建与并发用户数量相同的线程,因为您的代理人数量可能会增加。


-1

在升级到.NET 4之前,线程池可能是您最好的选择。您还可以使用Semaphore和AutoResetEvent来控制并发线程的数量。如果您正在谈论长时间运行的工作,则启动和管理自己的线程的开销很低,解决方案更加优雅。这将允许您使用WorkerThread.Join(),以便在恢复执行之前确保所有工作线程都已完成。


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