FileSystemWatcher无法访问文件,因为在其他进程中使用

时间:2017-11-16 13:05:15

标签: c# filesystemwatcher

我正在编写一个FileSystemWatcher,用于将图像从文件夹A复制到文件夹B,只要将图像上传到文件夹A.我试图将其用作服务器PC上的Windows服务但是我有一些问题,我的文件在被复制时被锁定。我想我已经找到了问题的根源,但我没有解决它的运气。因此,当我运行我的Windows服务时,它总是在第一次或第二次上传图片时意外结束。我收到的错误消息是:The process cannot access the file 'filepath' because it is being used by another process.

我的代码的相关部分:

public void WatchForChanges()
{
    FileSystemWatcher watcher = new FileSystemWatcher();
    watcher.Path = Program.SourceFolder;
    watcher.Created += new FileSystemEventHandler(OnImageAdded);
    watcher.EnableRaisingEvents = true;
    watcher.IncludeSubdirectories = true;
}

public void OnImageAdded(object source, FileSystemEventArgs e)
{
    FileInfo file = new FileInfo(e.FullPath);
    ImageHandler handler = new ImageHandler();
    if (handler.IsImage(file))
    {
        handler.CopyImage(file);
    }
}

和我的CopyImage方法,其中包含我提出的一个解决此问题的方法,利用一个捕获错误并重试复制图像的while循环:

public void CopyImage(FileSystemInfo file)
{
    // code that sets folder paths
    // code that sets folder paths

    bool retry = true;
    if (!Directory.Exists(targetFolderPath))
    {
        Directory.CreateDirectory(targetFolderPath);
    }
    while (retry)
    {
        try
        {
            File.Copy(file.FullName, targetPath, true);
            retry = false;
        }
        catch (Exception e)
        {
            Thread.Sleep(2000);
        }
    }
}

但是这个CopyImage解决方案只是继续复制同一个文件,这在我的情况下并不是很理想。我希望这已经足够了,但遗憾的是我已经有了等待的图像队列。

2 个答案:

答案 0 :(得分:1)

图像文件可能是由另一个在读取和写入外部进程时使用独占访问锁定的应用程序创建的(有关更多信息,请阅读this,尤其是与Microsoft Windows相关的段落)。你必须要么:

  • 停止/终止正在使用该文件的进程;
  • 等到文件不再使用。

由于其他进程可能在您尝试使用您的应用程序复制文件时编写该文件,因此第一个选项绝不是值得推荐的。它也可能是一个检查新文件的防病毒软件,即使在这种情况下,第一个选项也不值得推荐。

您可以尝试将以下代码集成到CopyImage方法中,以便应用程序等到文件不再使用之后再继续:

private Boolean WaitForFile(String filePath)
{
    Int32 tries = 0;

    while (true)
    {
        ++tries;

        Boolean wait = false;
        FileStream stream = null;

        try
        {
            stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
            break;
        }
        catch (Exception ex)
        {
            Logger.LogWarning("CopyImage({0}) failed to get an exclusive lock: {1}", filePath, ex.ToString());

            if (tries > 10)
            {
                Logger.LogWarning("CopyImage({0}) skipped the file after 10 tries.", filePath);
                return false;
            }

            wait = true;
        }
        finally
        {
            if (stream != null)
                stream.Close();
        }

        if (wait)
            Thread.Sleep(250);
    }

    Logger.LogWarning("CopyImage({0}) got an exclusive lock after {1} tries.", filePath, tries);

    return true;
}

答案 1 :(得分:0)

虽然看起来很简单,但实际上并不令人满意。

问题是,当你收到通知时,编写文件的应用程序没有完成...所以你有一个并发问题。没有什么好方法可以知道文件关闭的时间。嗯..一种方式是订阅日志事件 - 这是FileSystemWatcher所做的 - 但这是相当复杂的,需要一个很多的移动部件。走这条路线,您可以在文件关闭时收到通知。如果您有兴趣,请参阅https://msdn.microsoft.com/en-us/library/windows/desktop/aa363798(v=vs.85).aspx

我将工作分为两部分。我想我开始一个ThreadPool线程来完成这项工作,并让它从FileSystemWatcher's事件处理程序写入的列表中读取它的工作。这样,事件处理程序快速返回。 ThreadPool线程将通过它的列表,尝试获取文件上的独占锁(类似于Tommaso的代码)。如果它不能,它只是移动到下一个文件。每次成功复制时,它都会从列表中删除该文件。

您需要关注线程安全性......因此您需要创建一个静态对象来协调对列表的写入。事件处理程序和ThreadPool线程都会在写入时保持锁定。

这是整个方法的支架:

internal sealed class Copier: IDisposable
{
  static object sync = new object();
  bool quit;
  FileSystemWatcher watcher;
  List<string> work;

  internal Copier( string pathToWatch )
  {
    work = new List<string>();
    watcher = new FileSystemWatcher();
    watcher.Path = pathToWatch;
    watcher.Create += QueueWork;
    ThreadPool.QueueUserWorkItem( TryCopy );
  }

  void Dispose()
  {
    lock( sync ) quit = true;
  }

  void QueueWork( object source, FileSystemEventArgs args )
  {
    lock ( sync )
    {
      work.Add( args.FullPath );
    }
  }

  void TryCopy( object args )
  {
    List<string> localWork;
    while( true )
    {
      lock ( sync ) 
      { 
        if ( quit ) return;  //--> we've been disposed
        localWork = new List<string>( work );
      }
      foreach( var fileName in localWork )
      {
        var locked = true;
        try
        {
          using 
          ( var throwAway = new FileStream
            ( fileName, 
              FileMode.Open,
              FileAccess.Read,
              FileShare.None
            )
          ); //--> no-op  - will throw if we can't get exclusive read        
          locked = false;
        }
        catch { }
        if (!locked )
        {
          File.Copy( fileName, ... );
          lock( sync ) work.Remove( fileName );
        }
      }
    }
  }
}

没有经过测试 - 在答案中写到这里......但它或类似的内容将涵盖基础。