C#中的多线程应用程序

时间:2012-12-06 08:10:19

标签: c# .net

我已经创建了一个用于优化pdf文件的.net应用程序。实际上我必须优化许多文件,我已经调用了这样的线程:

CheckForIllegalCrossThreadCalls = false;
thOptimize = new Thread(csCommon.pdfFilesCompressAndMove);
thOptimize.Start();

我也发现了没有。处理器和核心使用此:

int processors=Environment.ProcessorCount
int coreCount = 0;
foreach (var item in new System.Management.ManagementObjectSearcher("Select * from  Win32_Processor").Get())
{
coreCount += int.Parse(item["NumberOfCores"].ToString());
}

我在我的机器上找到了4个处理器和2个核心。

现在我的问题是我想对所有处理器使用函数pdfFilesCompressAndMove,即我想同时优化多个文件。换句话说,我想让所有处理器忙于优化。

请指导我怎么可能?

4 个答案:

答案 0 :(得分:1)

您想要的是生产者/消费者队列。

这里发生的是生产者创建供消费者处理的工作项。当生产者可以比消费者处理消费者更快的速度为消费者创造工作时,这种方法很有效。然后,您有一个或多个消费者处理此工作队列。

这是我用于此类事情的生产者消费者类:

public class ProducerConsumer<T>:IDisposable 
    {
        private  int _consumerThreads;
        private readonly Queue<T> _queue = new Queue<T>();
        private readonly object _queueLocker = new object();
        private readonly AutoResetEvent _queueWaitHandle = new AutoResetEvent(false);
        private readonly Action<T> _consumerAction;
        private readonly log4net.ILog _log4NetLogger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private bool _isProcessing = true;

        public ProducerConsumer(Action<T> consumerAction,int consumerThreads,bool isStarted)
        {
            _consumerThreads = consumerThreads;

            if (consumerAction == null)
            {
                throw new ArgumentNullException("consumerAction");
            }
            _consumerAction = consumerAction;
            if (isStarted)
                Start();
            //just in case the config item is missing or is set to 0.  We don't want to have the queue build up
        }

        public ProducerConsumer(Action<T> consumerAction, int consumerThreads):this(consumerAction,consumerThreads,true)
        {


        }
        public void Dispose()
        {
            _isProcessing = false;
            lock(_queueLocker)
            {
                _queue.Clear();
            }
        }
        public void Start()
        {
            if (_consumerThreads == 0)
                _consumerThreads = 2;

            for (var loop = 0; loop < _consumerThreads; loop++)
                ThreadPool.QueueUserWorkItem(ConsumeItems);
        }

        public void Enqueue(T item)
        {
            lock (_queueLocker)
            {
                _queue.Enqueue(item);
                // After enqueuing the item, signal the consumer thread.            
                _queueWaitHandle.Set();
            }
        }

        private void ConsumeItems(object state)
        {
            while (_isProcessing)
            {
                try
                {
                    var nextItem = default(T);
                    bool doesItemExist;
                    lock (_queueLocker)
                    {
                        int queueCount = _queue.Count;
                        doesItemExist = queueCount > 0;
                        if (doesItemExist)
                        {
                            nextItem = _queue.Dequeue();
                        }
                        if (queueCount > 0 && queueCount % 50 == 0)
                            _log4NetLogger.Warn(String.Format("Queue is/has been growing.  Queue size now:{0}",
                                                              queueCount));
                    }
                    if (doesItemExist)
                    {
                        _consumerAction(nextItem);
                    }
                    else
                    {
                        _queueWaitHandle.WaitOne();
                    }
                }
                catch (Exception ex)
                {

                    _log4NetLogger.Error(ex);
                }

            }
        }
    }

这是一个泛型类,因此T是您要处理的对象类型。您还为它提供了一个Action,它是执行实际处理的方法。这应该允许您以干净的方式一次处理多个PDF文件。

答案 1 :(得分:0)

检查此主题:Optimal number of threads per core

如果您的线程方法csCommon.pdfFilesCompressAndMove非常耗费CPU(我可以通过其名称猜测),那么每个核心应该启动1个线程。您最好使用ThreadPool.QueueUserWorkItem,而不是手动创建线程,它将负责在核心之间生成线程。 在你的情况下,据我所知,你有8个内核,所以你可以调用ThreadPool.QueueUserWorkItem(csCommon.pdfFilesCompressAndMove) 8次,并在你的一个线程完成时再次调用,保持运行线程的总数等于8。

答案 2 :(得分:0)

我会使用ThreadPool,因为据我所知,它由.NET Framework和操作系统管理,总是为目标系统创建最佳线程数。

答案 3 :(得分:0)

我认为您最好的选择是从简单的事情开始,这样您就可以了解问题的性能特征。

List<string> items = GetListOfPdfFilesToProcess();
int numCores = 4;
int maxListChunkSize = (int)Math.Ceiling(items.Count / (double)numCores);
ManualResetEvent[] events = new ManualResetEvent[numCores];

for (int i = 0; i < numCores; i++)
{
    ThreadPool.QueueUserWorkItem(ProcessFiles, new object[]
    {
        items.Skip(i * maxListChunkSize).Take(maxListChunkSize).ToList(), events[i]
    });
}

WaitHandle.WaitAll(events);

....

private static void ProcessFiles(object state)
{
    object[] stateArray = (object[])state;
    List<string> filePaths = (List<string>)stateArray[0];
    ManualResetEvent completeEvent = (ManualResetEvent)stateArray[1];

    for (int i = 0; i < filePaths.Count; i++)
    {
        csCommon.pdfFilesCompressAndMove(your parameters);
    }

    completeEvent.Set();
}

这里最重要的是将工作分成numCores个块。通过这种方式,您应该能够充分利用所有CPU内核,但保留一个非常简单的编程模型。

请记住,这不会进行任何错误处理 - 您需要处理此问题。如果csCommon.pdfFilesCompressAndMove无法处理文件,也可以考虑做些什么。最简单的方法是记录错误并稍后检查,但如果您认为下次再次成功,可以尝试重新处理该文件。

您会注意到state对象只是一个数组;如果您需要将大量参数传递给ProcessFiles,那么将这些参数包装到单个对象中并将其作为state传递可能更简单。

修改

Tick事件中使用:

private void TimerTick(object sender, EventArgs e)
{
    //Disabling the timer will ensure the `TimerTick` method will not try to run
    //while we are processing the files. This covers the case where processing takes
    //longer than 2 minutes.
    timer.Enabled = false;

    //Run the first block of code in my answer.

    //Reenabling the timer will start the polling back up.
    timer.Enabled = true;
}

我还建议检查您必须处理的文件数:如果没有,请重新启用计时器并返回。这将避免排队一堆实际上没有做任何事情的操作。