现在的位置: 首页 > 综合 > 正文

一个FileSystemWatcher和线程池的问题

2011年12月27日 ⁄ 综合 ⁄ 共 1734字 ⁄ 字号 评论关闭

说下有问题的程序,首先建立一个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: }

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

抱歉!评论已关闭.