确保ProcessingQueue.Count在多个线程应用程序中正确

时间:2014-01-08 15:21:39

标签: c# multithreading

我有一个Windows服务来处理链表队列中的xml文件。创建文件时,FileSystemWatcher事件添加了队列中的文件。

namespace XMLFTP
{
    public class XML_Processor : ServiceBase
    {
       public string s_folder { get; set; }
       public XML_Processor(string folder)
       {
           s_folder = folder;
       }
       Thread worker;
       FileSystemWatcher watcher;
       DirectoryInfo my_Folder;
       public static AutoResetEvent ResetEvent { get; set; }
       bool running;
       public bool Start()
       {
           my_Folder = new DirectoryInfo(s_folder);
           bool success = true;
           running = true;
           worker = new Thread(new ThreadStart(ServiceLoop));
           worker.Start();
           // add files to queue by FileSystemWatcher event
           return (success);
       }
       public bool Stop()
       {
           try
           {
               running = false;
               watcher.EnableRaisingEvents = false;
               worker.Join(ServiceSettings.ThreadJoinTimeOut);
           }
           catch (Exception ex)
           {
               return (false);
           } 
           return (true);
       }
       public void ServiceLoop()
       {
           string fileName;
           while (running)
           {
               Thread.Sleep(2000);
               if (ProcessingQueue.Count > 0)
               {
                   // process file and write info to DB. 
               }
           }
       }

       void watcher_Created(object sender, FileSystemEventArgs e)
       {
           switch (e.ChangeType)
           {
               case WatcherChangeTypes.Created:// add files to queue
           }
       } 
    }
 }

可能存在线程安全问题。

        while (running)
        {
            Thread.Sleep(2000);
            if (ProcessingQueue.Count > 0)
            {
                // process file and write info to DB. 
            }
        }

由于对ProcessingQueue.Count的访问不受锁保护,如果另一个线程改变"队列",则Count可以改变。结果,进程文件部分可能会失败。如果您将Count属性实现为:

,情况也是如此
public static int Count
{
get { lock (syncRoot) return _files.Count; }
}

因为锁被释放到了早期。

我的两个问题:

  1. 如何使ProcessingQueue.Count正确?
  2. 如果我使用.NET Framework 4.5 BlockingCollection技能,示例代码为:

     class ConsumingEnumerableDemo
     {
        // Demonstrates: 
        //      BlockingCollection<T>.Add() 
        //      BlockingCollection<T>.CompleteAdding() 
        //      BlockingCollection<T>.GetConsumingEnumerable() 
        public static void BC_GetConsumingEnumerable()
        {
            using (BlockingCollection<int> bc = new BlockingCollection<int>())
            {
    
                // Kick off a producer task
                Task.Factory.StartNew(() =>
                {
                    for (int i = 0; i < 10; i++)
                    {
                        bc.Add(i);
                        Thread.Sleep(100); // sleep 100 ms between adds
                    }
    
                    // Need to do this to keep foreach below from hanging
                    bc.CompleteAdding();
                });
    
                // Now consume the blocking collection with foreach. 
                // Use bc.GetConsumingEnumerable() instead of just bc because the 
                // former will block waiting for completion and the latter will 
                // simply take a snapshot of the current state of the underlying collection. 
                foreach (var item in bc.GetConsumingEnumerable())
                {
                    Console.WriteLine(item);
                }
             }
           }
         }
    
  3. 该示例使用常量10作为iteration-clause,如何将队列中的动态计数应用于它?

1 个答案:

答案 0 :(得分:3)

使用BlockingCollection,您无需知道计数。消费者知道在队列为空并且IsCompleted为真之前保持处理项目。所以你可以这样:

var producer = Task.Factory.StartNew(() =>
{
    // Add 10 items to the queue
    foreach (var i in Enumerable.Range(0, 10))
        queue.Add(i);

    // Wait one minute
    Thread.Sleep(TimeSpan.FromMinutes(1.0));

    // Add 10 more items to the queue
    foreach (var i in Enumerable.Range(10, 10))
        queue.Add(i);

    // mark the queue as complete for adding
    queue.CompleteAdding();
});

// consumer
foreach (var item in queue.GetConsumingEnumerable())
{
    Console.WriteLine(item);
}

消费者将输出前10个项目,这会清空队列。但由于生产者没有调用CompleteAdding,消费者将继续阻止队列。它将捕获生产者写下的10个项目。然后,队列为空并且IsCompleted == true,因此消费者结束(GetConsumingEnumerable到达队列的末尾)。

您可以随时查看Count,但您获得的价值只是快照。在您评估它时,生产者或消费者可能会修改队列并更改计数。但这应该不重要。只要您不拨打CompleteAdding,消费者就会继续等待某个项目。

生产者编写的项目数不必是常数。例如,在我的Simple Multithreading博文中,我展示了一个生产者,它读取文件并将项目写入由消费者提供服务的BlockingCollection。生产者和消费者同时运行,一切都进行到生产者到达文件末尾。