web-dev-qa-db-fra.com

Attendre que le fichier soit complètement écrit

Quand un fichier est créé (FileSystemWatcher_Created) dans un répertoire, je le copie dans un autre. Mais lorsque je crée un gros fichier (> 10 Mo), il ne parvient pas à copier le fichier car il commence déjà à copier, alors que le fichier n'a pas encore fini de créer ...
Ceci provoque Impossible de copier le fichier, car il est utilisé par un autre processus à être généré. ; (
De l'aide?

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();
    }
}
63
levi

Il n'y a qu'une solution de contournement au problème que vous rencontrez.

Vérifiez si l'id du fichier est en cours avant de lancer le processus de copie. Vous pouvez appeler la fonction suivante jusqu'à obtenir la valeur False.

1ère méthode, copiée directement de cette réponse :

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;
}

2ème méthode:

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;
}
40
Romil Kumar Jain

De la documentation pour FileSystemWatcher:

L'événement OnCreated est levé dès qu'un fichier est créé. Si un fichier est copié ou transféré dans un répertoire surveillé, l'événement OnCreated sera immédiatement généré, suivi d'un ou plusieurs événements OnChanged.

Donc, si la copie échoue, (attrapez l'exception), ajoutez-la à une liste de fichiers qui doivent encore être déplacés et tentez la copie pendant l'événement OnChanged. Finalement, ça devrait marcher.

Quelque chose comme (incomplet; capture des exceptions spécifiques, initialise des variables, etc.):

    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 {}
         }
   }
10
Steve Czetty

C'est un vieux fil, mais je vais ajouter des informations pour d'autres personnes.

J'ai rencontré un problème similaire avec un programme qui écrit PDF fichiers, il faut parfois 30 secondes pour le rendu .. qui correspond à la période pendant laquelle ma classe watcher_FileCreated attend avant de copier le fichier.

Les fichiers n'étaient pas verrouillés.

Dans ce cas, j'ai vérifié la taille du fichier PDF), puis ai attendu 2 secondes avant de comparer la nouvelle taille. S'ils n'étaient pas égaux, le fil resterait en veille pendant 30 secondes et réessayer.

10
Johnny Grimes

En fait, vous avez de la chance. Le programme qui écrit le fichier le verrouille, vous ne pouvez donc pas l'ouvrir. S'il ne l'avait pas verrouillé, vous auriez copié un fichier partiel, sans vous en rendre compte.

Lorsque vous ne pouvez pas accéder à un fichier, vous pouvez en déduire qu'il est toujours utilisé (mieux encore: essayez de l'ouvrir en mode exclusif et vérifiez si quelqu'un d'autre l'ouvre actuellement au lieu de deviner à partir de l'échec de File.Copy). Si le fichier est verrouillé, vous devrez le copier à un autre moment. Si ce n'est pas verrouillé, vous pouvez le copier (il y a un léger potentiel pour une condition de concurrence critique).

Quand est-ce que "l'autre temps"? Je ne me rappelle plus quand FileSystemWatcher envoie plusieurs événements par fichier - vérifiez-le, il vous suffira peut-être simplement d'ignorer l'événement et d'en attendre un autre. Sinon, vous pouvez toujours définir une heure et revérifier le fichier en 5 secondes.

5
zmbq

Eh bien, vous donnez déjà la réponse vous-même; vous devez attendre la fin de la création du fichier. Une façon de le faire consiste à vérifier si le fichier est toujours utilisé. Un exemple de ceci peut être trouvé ici: Y a-t-il un moyen de vérifier si un fichier est en cours d'utilisation?

Notez que vous devrez modifier ce code pour qu'il fonctionne dans votre situation. Vous voudrez peut-être quelque chose comme (pseudocode):

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

   CopyFile()
}

Évidemment, vous devriez vous protéger d'un infini while juste au cas où l'application propriétaire ne relâche jamais le verrou. En outre, il pourrait être intéressant de consulter les autres événements de FileSystemWatcher auxquels vous pouvez vous abonner. Il peut y avoir un événement que vous pouvez utiliser pour contourner tout ce problème.

2
pyrocumulus

Ainsi, après avoir rapidement parcouru certaines de ces questions et d’autres questions similaires, je me suis lancé cet après-midi dans une joyeuse chasse à la poule pour tenter de résoudre un problème avec deux programmes distincts utilisant un fichier comme méthode de synchronisation (et de sauvegarde de fichier). Une situation un peu inhabituelle, mais elle a clairement mis en évidence pour moi les problèmes liés à la vérification du verrouillage du fichier, puis ouvrez-le s'il ne l'est pas.

Le problème est le suivant: le fichier peut devenir verrouillé entre le moment où vous le vérifiez et le moment où vous ouvrez le fichier. Il est très difficile de localiser le sporadique impossible de copier le fichier, car il est utilisé par un autre processus erreur si vous ne le recherchez pas également.

La résolution de base consiste simplement à essayer d'ouvrir le fichier à l'intérieur d'un bloc catch afin que, s'il est verrouillé, vous puissiez réessayer. De cette façon, il n'y a pas de temps écoulé entre la vérification et l'ouverture, le système d'exploitation les effectue en même temps.

Le code utilisé ici utilise File.Copy, mais il fonctionne tout aussi bien avec l’une des méthodes statiques de la classe File: File.Open, File.ReadAllText, File.WriteAllText, etc.

/// <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;
    }
}

ne autre petite note sur le parallélisme: C'est une méthode synchrone, qui bloquera son thread à la fois en attente et en travaillant sur le thread. C'est l'approche la plus simple, mais si le fichier reste verrouillé pendant une longue période, votre programme risque de ne plus répondre. Le parellélisme est un sujet trop vaste pour être approfondi ici (et le nombre de façons de configurer une lecture/écriture asynchrone est un peu absurde), mais voici une façon de le parelléliser.

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;
        }
    }
}

Et voici comment cela pourrait être utilisé:

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!
2
ashbygeek

Lorsque le fichier est écrit en binaire (octet par octet), créez FileStream et les solutions ci-dessus. Ne fonctionne pas, car le fichier est prêt et écrit dans chaque octet. Dans cette situation, vous avez besoin d'une autre solution de contournement semblable à celle-ci: Faites ceci lorsque le fichier est créé ou que vous voulez. pour commencer le traitement en fichier

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!
1
Mohsen.Sharify

Vous pouvez utiliser le code suivant pour vérifier si le fichier peut être ouvert avec un accès exclusif (autrement dit, il n'est pas ouvert par une autre application). Si le fichier n'est pas fermé, vous pouvez attendre quelques instants et vérifier à nouveau jusqu'à la fermeture du fichier pour pouvoir le copier en toute sécurité.

Vous devez quand même vérifier si File.Copy échoue, car une autre application peut ouvrir le fichier entre le moment où vous vérifiez le fichier et celui où vous le copiez.

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;
    }
}
0
Michael