一个FileSystemWatcher和线程池的问题

说下有问题的程序,首先建立一个FileSystemWatcher,监控目录是否有新的文件到达,如果到达了就线程池分配一个线程来读取文件,然后进行后续处理,思路很简单,代码如下:

   1: private readonly FileSystemWatcher _watcher;
   2:  
   3: public IntrayWatcher(string path)
   4: {           
   5:     _watcher = new FileSystemWatcher
   6:                   {
   7:                       Path = path,
   8:                       Filter = Constants.Configuration.ExcelFilter,
   9:                       NotifyFilter = NotifyFilters.FileName |
  10:                                      NotifyFilters.LastWrite |
  11:                                      NotifyFilters.CreationTime
  12:                   };
  13:  
  14:     _watcher.Created += OnNewFileComesin;
  15:     _watcher.EnableRaisingEvents = true;
  16: }
  17:  
  18: private void OnNewFileComesin(object sender, FileSystemEventArgs e)
  19: {
  20:     Task.Factory.StartNew(() =>
  21:               {   
  22:                   return ReadFile(e.FullPath);
  23:               })
  24:               .ContinueWith( 
  25:                 //....
  26:                );
  27: }
  28:  
  29: private object ReadFile(string filePath)
  30: {
  31:    using (var stream = new StreamReader(fullPath))
  32:    {
  33:                 //...
  34:                 return data;
  35:    }
  36: }

思路很清晰,代码很清晰,实际运行发现一个bug,那就是第一次往文件夹里面Copy一个新文件能正常运行,但是Copy第二个文件的时候就报一个文件正被其他线程占用无法打开的异常:

12-23-2011 11-53-32

想想也想不通,哪里来的其他进程也打开了这个文件,导致文件无法打开?后来发现,原来是FileSystemWatcher有个问题(也可以说bug吧),就是当新文件到达了以后,这个Watcher太灵敏,文件到达了,IO拷贝还没有完成,其Created事件就已经触发了。也就是说,FileSystemWatcher的Created事件不是在新文件到达拷贝完成的时候触发的,而是这个事件在文件一创建还没有IO拷贝完成的时候就触发了。这就造成两个线程在读取同一个文件的异常了。

为什么第二次才出现问题?

为什么第一次文件拷贝进来就能正常工作, 而当拷贝进来第二个文件就报异常呢? 而且我还发现第一次即使一次同时拷贝N个文件进来也没有问题。这是为什么?想了一会儿,原来是这样的:

当第一个文件进来的时候,Task.Factory.StartNew这句话是线程池创建新的线程,这个操作是异步的,创建线程,切换线程需要CPU和内存开销,而且过程又是异步的,所以首次会慢一点,不会抢到IO拷贝的进程。第二个文件进来的时候,由于程序空闲,Task.Factory.StartNew这句话是线程池分配空闲线程(还是原来那个线程号),这个步骤非常快,就抢到IO拷贝的进程。至于为什么第一次即使一次同时拷贝N个文件进来也没有问题,原因是他们并发到来,当时Task.Factory.StartNew这句话是线程池创建N个不同的新的线程。所以原因和一个文件的时候一样。(不知道我理解正确否?)

Thread.Sleep()?

解决办法很简单了,就是在读取文件前加一行:Thread.Sleep(100); 难道解决问题的办法就是让线程睡觉?看到老外也有这样的,原理一样的:

   1: private void WaitForFile(string fullPath)
   2: {
   3:     while (true)
   4:     {
   5:         try
   6:         {
   7:             using (var stream = new StreamReader(fullPath))
   8:             {
   9:                 break;
  10:             }
  11:         }
  12:         catch
  13:         {
  14:             Thread.Sleep(100);
  15:         }
  16:     }
  17: }

不知道大家有没有其它办法呢?

posted on 2011-12-23 12:55  Mainz  阅读(2846)  评论(11编辑  收藏  举报

导航