C#多个后台工作线程

11
我试图设置多个BackgroundWorker来处理工作,并在空闲时开始处理下一部分工作。但我似乎无法使它们正常工作。我有以下代码。
当我将FilesToProcess设置为小于或等于MaxThreads时,它完美地工作,但如果我将其设置为更高,则应用程序会冻结。
我确定这是一个简单的问题,但我就是看不出来。
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace bgwtest
{
    public partial class Form1 : Form
    {
        private const int MaxThreads = 20;
        private const int FilesToProcess = 21;
        private BackgroundWorker[] threadArray = new BackgroundWorker[MaxThreads];

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1Load(object sender, EventArgs e)
        {
            InitializeBackgoundWorkers();
        }

        private void InitializeBackgoundWorkers()
        {
            for (var f = 0; f < MaxThreads; f++)
            {
                threadArray[f] = new BackgroundWorker();
                threadArray[f].DoWork += new DoWorkEventHandler(BackgroundWorkerFilesDoWork);
                threadArray[f].RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerFilesRunWorkerCompleted);
                threadArray[f].WorkerReportsProgress = true;
                threadArray[f].WorkerSupportsCancellation = true;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            for (var f = 0; f < FilesToProcess; f++)
            {
                var fileProcessed = false;
                while (!fileProcessed)
                {
                    for (var threadNum = 0; threadNum < MaxThreads; threadNum++)
                    {
                        if (!threadArray[threadNum].IsBusy)
                        {
                            Console.WriteLine("Starting Thread: {0}", threadNum);

                            threadArray[threadNum].RunWorkerAsync(f);
                            fileProcessed = true;
                            break;
                        }
                    }
                    if (!fileProcessed)
                    {
                        Thread.Sleep(50);
                    }
                }
            }
        }

        private void BackgroundWorkerFilesDoWork(object sender, DoWorkEventArgs e)
        {
            ProcessFile((int)e.Argument);

            e.Result = (int)e.Argument;
        }

        private static void ProcessFile(int file)
        {
            Console.WriteLine("Processing File: {0}", file);
        }

        private void BackgroundWorkerFilesRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }

            Console.WriteLine("Processed File: {0}", (int)e.Result);
        }
    }
}

1
我知道你在问 BackgroundWorker,但为什么不使用TPL或Rx呢?它们会使这个过程变得更加简单。 - Enigmativity
3
你正在通过在UI线程上休眠来创建死锁。这将阻止RunWorkerCompleted事件处理程序的运行。 - Hans Passant
2个回答

12

问题似乎是您的工作线程从未完成。我不确定原因,但这与运行它们的方法(和线程)本身未完成有关。我通过创建另一个工作线程来将文件分配给工作线程数组来解决了这个问题:

    private BackgroundWorker assignmentWorker;

    private void InitializeBackgoundWorkers() {
        assignmentWorker = new BackgroundWorker();
        assignmentWorker.DoWork += AssignmentWorkerOnDoWork;
        // ...
    }

    private void AssignmentWorkerOnDoWork( object sender, DoWorkEventArgs doWorkEventArgs ) {
        for( var f = 0; f < FilesToProcess; f++ ) {
            var fileProcessed = false;
            while( !fileProcessed ) {
                for( var threadNum = 0; threadNum < MaxThreads; threadNum++ ) {
                    if( !threadArray[threadNum].IsBusy ) {
                        Console.WriteLine( "Starting Thread: {0}", threadNum );

                        threadArray[threadNum].RunWorkerAsync( f );
                        fileProcessed = true;
                        break;
                    }
                }
                if( !fileProcessed ) {
                    Thread.Sleep( 50 );
                    break;
                }
            }
        }
    }

    private void button1_Click( object sender, EventArgs e ) {
        assignmentWorker.RunWorkerAsync();
    }

我对这个答案不满意,因为我不知道为什么它没有按照最初的设计工作。也许其他人可以回答这个问题...? 至少这样可以让你得到一个可工作的版本。

编辑:你最初的版本不起作用是因为BackgroundWorkerFilesRunWorkerCompleted在与button1_Click(UI线程)相同的线程上运行。由于您没有释放UI线程,因此该线程永远不会被标记为完成。


1
作为对Ethan Brown回答的补充,问题实际上来自于您在UI线程中运行一个永远忙碌的循环。
如果filesToProcess小于最大线程数,循环将为每个文件分配一个线程并退出。没有问题。
但是,如果您有更多的文件需要处理,而所有线程都已经运行,则在尝试处理文件时会发生以下情况:
- 您检查所有线程是否繁忙,是的,它们都是。 - 您通过执行线程休眠等待。
问题在于,当后台工作者完成时,它将向UI线程消息队列发布一条消息。而您的UI线程实际上正在忙于检查线程并等待。
要解决这个问题:
- 要么将循环放入另一个线程(如Ethan Brown的解决方案); - 要么在Thread.Sleep()之前或之后添加Application.DoEvents()以强制UI线程处理消息。这不是很好的做法,但非常好地说明了这里的问题。

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