等待文件完全写入


77

在一个目录中创建文件(FileSystemWatcher_Created)后,我将其复制到另一个目录。但是,当我创建一个较大的文件(> 10MB)时,它无法复制文件,因为它已经开始复制,而文件还没有完成创建。
这导致无法复制文件,因为它被另一个进程使用。提高。;(有
什么帮助吗?

class Program
{
    static void Main(string[] args)
    {
        string path = @"D:\levan\FolderListenerTest\ListenedFolder";
        FileSystemWatcher listener; 
        listener = new FileSystemWatcher(path);
        listener.Created += new FileSystemEventHandler(listener_Created);
        listener.EnableRaisingEvents = true;

        while (Console.ReadLine() != "exit") ;
    }

    public static void listener_Created(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine
                (
                    "File Created:\n"
                   + "ChangeType: " + e.ChangeType
                   + "\nName: " + e.Name
                   + "\nFullPath: " + e.FullPath
                );
        File.Copy(e.FullPath, @"D:\levan\FolderListenerTest\CopiedFilesFolder\" + e.Name);
        Console.Read();
    }
}

1
我找到答案在这里对不起[这个问题解决了在计算器上] [1] [1]:stackoverflow.com/questions/1406808/...
列维

Answers:


44

对于您面临的问题,只有解决方法。

开始复制之前,请检查文件ID是否正在处理中。您可以调用以下函数,直到获得False值为止。

第一种方法,直接从此答案复制:

private bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}

第二种方法:

const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
private bool IsFileLocked(string file)
{
    //check that problem is not in destination file
    if (File.Exists(file) == true)
    {
        FileStream stream = null;
        try
        {
            stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
        }
        catch (Exception ex2)
        {
            //_log.WriteLog(ex2, "Error in checking whether file is locked " + file);
            int errorCode = Marshal.GetHRForException(ex2) & ((1 << 16) - 1);
            if ((ex2 is IOException) && (errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION))
            {
                return true;
            }
        }
        finally
        {
            if (stream != null)
                stream.Close();
        }
    }
    return false;
}

2
而且您忘了复制常量值(ERROR_SHARING_VIOLATION= 32,ERROR_LOCK_VIOLATION= 33)
朱利安N

@RomilKumarJain我们可以看到此方法的用法吗?
Roxy'Pro

@ Roxy'Pro您的用法是什么意思?
Romil Kumar Jain

14

从文档中FileSystemWatcher

OnCreated创建文件后立即引发该事件。如果文件正在复制或转移到监视的目录中,OnCreated则将立即引发该 事件,然后是一个或多个 OnChanged事件。

因此,如果复制失败(捕获异常),请将其添加到仍需要移动的文件列表中,然后在OnChanged事件期间尝试复制。最终,它应该工作。

诸如此类(不完整;捕获特定的异常,初始化变量等):

public static void listener_Created(object sender, FileSystemEventArgs e)
{
    Console.WriteLine
            (
                "File Created:\n"
               + "ChangeType: " + e.ChangeType
               + "\nName: " + e.Name
               + "\nFullPath: " + e.FullPath
            );
    try {
        File.Copy(e.FullPath, @"D:\levani\FolderListenerTest\CopiedFilesFolder\" + e.Name);
    }
    catch {
        _waitingForClose.Add(e.FullPath);
    }
    Console.Read();
}

public static void listener_Changed(object sender, FileSystemEventArgs e)
{
     if (_waitingForClose.Contains(e.FullPath))
     {
          try {
              File.Copy(...);
              _waitingForClose.Remove(e.FullPath);
          }
          catch {}
     }

}


这是一个很好的方法,但是我立刻需要它,所以我把IsFileReady放了一段时间,现在就可以工作了
levi

11

这是一个旧线程,但我将为其他人添加一些信息。

我在写PDF文件的程序中遇到了类似的问题,有时它们需要30秒才能呈现..这与我的watcher_FileCreated类在复制文件之前等待的时间相同。

文件未锁定。

在这种情况下,我检查了PDF的大小,然后等待2秒钟再比较新的大小,如果它们不相等,则该线程将休眠30秒钟,然后重试。


5

您实际上很幸运-编写文件的程序将其锁定,因此您无法打开它。如果它没有锁定它,那么您将复制一个部分文件,而不会有任何问题。

当您无法访问文件时,可以假设它仍在使用中(更好的是-尝试以独占模式打开它,并查看是否有人正在打开它,而不是从File.Copy的失败中猜测)。如果文件已锁定,则必须在其他时间复制它。如果未锁定,则可以将其复制(此处可能存在竞争状况)。

什么时候是“其他时间”?我不记得当FileSystemWatcher每个文件发送多个事件时-签出它,您只需忽略该事件并等待另一个事件就足够了。如果没有,您可以随时设置时间并在5秒钟内重新检查文件。


3

好吧,您已经自己给出了答案;您必须等待文件创建完成。一种方法是通过检查文件是否仍在使用中。可以在这里找到一个示例:是否可以检查文件是否正在使用?

请注意,您必须修改此代码才能使其在您的情况下起作用。您可能想要类似(伪代码)的东西:

public static void listener_Created()
{
   while CheckFileInUse()
      wait 1000 milliseconds

   CopyFile()
}

显然while,以防万一所有者应用程序永远不会释放锁,您应该保护自己免受无限侵害。另外,可能值得检查一下FileSystemWatcher您可以订阅的其他事件。您可能会使用某个事件来规避整个问题。


2

当文件以二进制(逐字节)写入时,创建FileStream及以上解决方案无法正常工作,因为文件已准备好并且每个字节都写满了,因此在这种情况下,您还需要其他解决方法:在创建文件时执行开始处理文件

long fileSize = 0;
currentFile = new FileInfo(path);

while (fileSize < currentFile.Length)//check size is stable or increased
{
  fileSize = currentFile.Length;//get current size
  System.Threading.Thread.Sleep(500);//wait a moment for processing copy
  currentFile.Refresh();//refresh length value
}

//Now file is ready for any process!

2

因此,在快速浏览了其中一些以及其他类似的问题之后,我今天下午进行了一次快乐的追赶,试图使用文件作为同步(以及文件保存)方法来解决两个独立程序的问题。有点不寻常的情况,但是对于我来说,它无疑突出了“检查文件是否被锁定,如果没有锁定则打开”问题。

问题是这样的:在您检查文件和实际打开文件之间,文件可能会锁定。很难找到偶发的“无法复制”文件,因为如果您也不想查找该文件,则另一个进程错误会使用它。

基本解决方案是仅尝试在catch块中打开文件,以便在文件被锁定时可以重试。这样,在支票和开票之间就没有经过的时间,操作系统会同时进行。

此处的代码使用File.Copy,但它与File类的任何静态方法也可以正常工作:File.Open,File.ReadAllText,File.WriteAllText等。

/// <param name="timeout">how long to keep trying in milliseconds</param>
static void safeCopy(string src, string dst, int timeout)
{
    while (timeout > 0)
    {
        try
        {
            File.Copy(src, dst);

            //don't forget to either return from the function or break out fo the while loop
            break;
        }
        catch (IOException)
        {
            //you could do the sleep in here, but its probably a good idea to exit the error handler as soon as possible
        }
        Thread.Sleep(100);

        //if its a very long wait this will acumulate very small errors. 
        //For most things it's probably fine, but if you need precision over a long time span, consider
        //   using some sort of timer or DateTime.Now as a better alternative
        timeout -= 100;
    }
}

关于parelelism的另一个小注意事项: 这是一个同步方法,它将在等待线程和在该线程上工作时阻塞其线程。这是最简单的方法,但是如果文件长时间保持锁定状态,则程序可能会无响应。并行语言学是一个太大的话题,无法在这里深入探讨(而且设置异步读/写的方法的数量是荒谬的),但这是可以并行化的一种方法。

public class FileEx
{
    public static async void CopyWaitAsync(string src, string dst, int timeout, Action doWhenDone)
    {
        while (timeout > 0)
        {
            try
            {
                File.Copy(src, dst);
                doWhenDone();
                break;
            }
            catch (IOException) { }

            await Task.Delay(100);
            timeout -= 100;
        }
    }

    public static async Task<string> ReadAllTextWaitAsync(string filePath, int timeout)
    {
        while (timeout > 0)
        {
            try {
                return File.ReadAllText(filePath);
            }
            catch (IOException) { }

            await Task.Delay(100);
            timeout -= 100;
        }
        return "";
    }

    public static async void WriteAllTextWaitAsync(string filePath, string contents, int timeout)
    {
        while (timeout > 0)
        {
            try
            {
                File.WriteAllText(filePath, contents);
                return;
            }
            catch (IOException) { }

            await Task.Delay(100);
            timeout -= 100;
        }
    }
}

这是如何使用它:

public static void Main()
{
    test_FileEx();
    Console.WriteLine("Me First!");
}    

public static async void test_FileEx()
{
    await Task.Delay(1);

    //you can do this, but it gives a compiler warning because it can potentially return immediately without finishing the copy
    //As a side note, if the file is not locked this will not return until the copy operation completes. Async functions run synchronously
    //until the first 'await'. See the documentation for async: https://msdn.microsoft.com/en-us/library/hh156513.aspx
    CopyWaitAsync("file1.txt", "file1.bat", 1000);

    //this is the normal way of using this kind of async function. Execution of the following lines will always occur AFTER the copy finishes
    await CopyWaitAsync("file1.txt", "file1.readme", 1000);
    Console.WriteLine("file1.txt copied to file1.readme");

    //The following line doesn't cause a compiler error, but it doesn't make any sense either.
    ReadAllTextWaitAsync("file1.readme", 1000);

    //To get the return value of the function, you have to use this function with the await keyword
    string text = await ReadAllTextWaitAsync("file1.readme", 1000);
    Console.WriteLine("file1.readme says: " + text);
}

//Output:
//Me First!
//file1.txt copied to file1.readme
//file1.readme says: Text to be duplicated!

0

您可以使用以下代码来检查是否可以通过独占访问打开文件(即,其他应用程序无法打开该文件)。如果文件没有关闭,您可以稍等片刻,然后再次检查直到文件关闭,然后可以安全地复制它。

您仍应检查File.Copy是否失败,因为在检查文件到复制之间,另一个应用程序可能会打开该文件。

public static bool IsFileClosed(string filename)
{
    try
    {
        using (var inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
        {
            return true;
        }
    }
    catch (IOException)
    {
        return false;
    }
}

如果遇到不是IOException的异常,此代码返回什么?
威廉·丹尼尔

如果File.Open引发IOException以外的其他异常,则此方法不返回值。检查可能引发的任何非IOException是呼叫者的责任:msdn.microsoft.com/zh-cn/library/y973b725(v=vs.110).aspx
Michael

-5

我想在这里添加答案,因为这对我有用。我使用了时间延迟,而while循环是我能想到的一切。

我打开了输出文件夹的Windows资源管理器窗口。我关闭它,一切都像个魅力。

我希望这可以帮助别人。


4
所以现在如果用户打开了该目录,您的应用就会崩溃?
Epirocks '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.