一个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第二个文件的时候就报一个文件正被其他线程占用无法打开的异常:
想想也想不通,哪里来的其他进程也打开了这个文件,导致文件无法打开?后来发现,原来是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: }
不知道大家有没有其它办法呢?