在控制台应用程序中使用.NET BackgroundWorker类

时间:2010-02-03 23:06:10

标签: .net multithreading backgroundworker

我对.NET编程和多线程一般都比较陌生,并且想知道是否可以使用.NET提供的BackgroundWorker来生成工作线程来在控制台应用程序中做一些工作?从在线的各种文档中,我看到这个类的意图更多的是面向UI的应用程序,你想在后台做一些工作,但保持UI响应,报告进度,取消处理,如果需要等。

在我的情况下,基本上我有一个控制器类,我想在多个工作线程中产生一些处理(限制使用信号量产生的最大工作线程数)。然后我希望我的控制器类阻塞,直到所有线程都完成处理。所以在我启动一个工作线程做一些工作后,我希望线程能够在处理完成时通知控制器线程。我看到我可以使用后台工作者类,并处理事件DoWork和RunWorkerCompleted来实现这一点,但是想知道这是不是一个好主意?有没有更好的方法来实现这一目标?

3 个答案:

答案 0 :(得分:9)

如果您的要求只是在所有线程完成之前阻止,那就非常简单 - 只需启动新线程,然后在每个线程上调用Thread.Join

using System;
using System.Collections.Generic;
using System.Threading;

public class Test
{
    static void Main()
    {
        var threads = new List<Thread>();
        for (int i = 0; i < 10; i++)
        {
            int copy = i;
            Thread thread = new Thread(() => DoWork(copy));
            thread.Start();
            threads.Add(thread);
        }

        Console.WriteLine("Main thread blocking");
        foreach (Thread thread in threads)
        {
            thread.Join();
        }
        Console.WriteLine("Main thread finished");
    }

    static void DoWork(int thread)
    {
        Console.WriteLine("Thread {0} doing work", thread);
        Random rng = new Random(thread); // Seed with unique numbers
        Thread.Sleep(rng.Next(2000));
        Console.WriteLine("Thread {0} done", thread);
    }
}

编辑:如果您可以访问.NET 4.0,那么TPL绝对是正确的方法。否则,我建议使用生产者/消费者队列(周围有大量的示例代码)。基本上你有一个工作项队列,以及你拥有核心的许多消费者线程(假设它们是CPU限制的;你想要根据你的工作量来定制它)。每个消费者线程都会从队列中获取项目并一次处理一个项目。你究竟如何管理这将取决于你的情况,但它并不是非常复杂。如果您能够提供开始时需要做的所有工作,那就更容易了,这样线程就可以在发现队列为空时退出。

答案 1 :(得分:8)

这在控制台应用程序中不起作用,因为永远不会初始化SynchronizationContext.Current。当您使用GUI应用程序时,这将由Windows窗体或WPF初始化。

话虽如此,没有理由这样做。只需使用ThreadPool.QueueUserWorkItem和重置事件(ManualResetEventAutoResetEvent)来捕获完成状态,并阻止主线程。


编辑:

在看到一些OP评论之后,我想我会加上这个。

在我看来,“最好的”替代方案是获取Rx Framework的副本,因为它包含.NET 4中TPL的后端。这将允许您使用{的重载{3}}提供了提供Parallel.ForEach实例的选项。这将允许您限制并发操作的总数,并为您处理所有工作:

// using collection of work items, such as: List<Action<object>> workItems;
var options = new ParallelOptions();
options.MaxDegreeOfParallelism = 10; // Restrict to 10 threads, not recommended!

// Perform all actions in the list, in parallel
Parallel.ForEach(workItems, options, item => { item(null); });

然而,使用Parallel.ForEach,我个人让系统管理并行度。它将自动分配适当数量的线程(特别是当/如果它移动到.NET 4)。

答案 2 :(得分:1)

你是对的,BackgroundWorker不会在这里工作。它确实设计用于GUI环境(WinForms和WPF等),其中主线程永远忙于检查/执行消息泵(处理所有Windows事件,如Click和Resize,以及来自backgroundWorker的那些)。 没有事件队列,Mainthread将耗尽。如果没有Event队列,BackgroundWorker也无法调用主线程上的Callbacks。

确实在控制台上进行多线程处理要困难十几倍,因为您没有消息泵来解决两个大问题:如何保持主线程处于活动状态以及如何通知UI上所需的主要线程更改。基于事件的编程有望成为多线程的理想学习基础。

有一些解决方案:

使用消息泵扩展您的控制台应用程序。因为它只是一个线程共享代表集合(其他线程只添加Stuff)和一个while循环,它非常简单: C# Console App + Event Handling

ThreadPool和Thread Classes。它们与BackgroundWorker(自.NET 2.0以来)一样长,并且似乎旨在解决Console的问题。您仍然需要通过循环阻止主线程。 ThreadPool还将线程数限制为Real机器上可适用的东西。您应该避免使用固定的线程限制,而是依赖操作系统的ThreadPooling功能来处理“一次运行多少线程?”的问题。 例如,固定的5个线程数可能是您的6核心开发机器的理想选择。但对于单核台式计算机来说太多了。对于服务器来说,这将是一个毫无意义的瓶颈,它可以轻松地在16到64个核心之间,并且可以使用GiB的RAM。

如果您使用的是.NET 4.0或更高版本,那么新添加的Task Paralellism可能值得一看。 http://msdn.microsoft.com/en-us/library/dd537609.aspx 它内置了ThreadPooling,有限的优先级功能和一个可以轻松链接和连接多个任务(线程可以做的一切,任务可以做)。 使用Task还可以使用async和await关键字: http://msdn.microsoft.com/en-us/library/hh191443.aspx 同样,您无法绕过控制台应用程序中的主线程。