112 votes

Attendez que le fichier soit déverrouillé dans .NET

Quelle est la façon la plus simple de bloquer un thread jusqu'à ce qu'un fichier a été débloqué et est accessible en lecture et en renommant? Par exemple, est-il un WaitOnFile() quelque part dans les .NET Framework?

J'ai un service qui utilise un FileSystemWatcher pour rechercher les fichiers qui doivent être transmises à un site FTP, mais le fichier créé événement se déclenche avant que l'autre ait fini d'écrire le fichier.

La solution idéale serait d'avoir un délai d'attente de sorte que le fil ne traîne pas toujours avant d'abandonner.

Edit: Après avoir essayé quelques solutions ci-dessous, j'ai fini par changer le système , de sorte que tous les fichiers écrivit Path.GetTempFileName(), ensuite effectué un File.Move() à l'emplacement définitif. Dès que l' FileSystemWatcher événement déclenché, le fichier était déjà terminée.

84voto

mafu Points 8920

À partir de la réponse d'Eric, j'ai ajouté quelques améliorations pour rendre le code beaucoup plus compact et réutilisable. J'espère que c'est utile.

 FileStream WaitForFile (string fullPath, FileMode mode, FileAccess access, FileShare share)
{
    for (int numTries = 0; numTries < 10; numTries++) {
        try {
            FileStream fs = new FileStream (fullPath, mode, access, share);

            fs.ReadByte ();
            fs.Seek (0, SeekOrigin.Begin);

            return fs;
        }
        catch (IOException) {
            Thread.Sleep (50);
        }
    }

    return null;
}
 

45voto

Eric Z Beard Points 18473

Voici la réponse que j'ai donnée à une question connexe :

     /// <summary>
    /// Blocks until the file is not locked any more.
    /// </summary>
    /// <param name="fullPath"></param>
    bool WaitForFile(string fullPath)
    {
        int numTries = 0;
        while (true)
        {
            ++numTries;
            try
            {
                // Attempt to open the file exclusively.
                using (FileStream fs = new FileStream(fullPath,
                    FileMode.Open, FileAccess.ReadWrite, 
                    FileShare.None, 100))
                {
                    fs.ReadByte();

                    // If we got this far the file is ready
                    break;
                }
            }
            catch (Exception ex)
            {
                Log.LogWarning(
                   "WaitForFile {0} failed to get an exclusive lock: {1}", 
                    fullPath, ex.ToString());

                if (numTries > 10)
                {
                    Log.LogWarning(
                        "WaitForFile {0} giving up after 10 tries", 
                        fullPath);
                    return false;
                }

                // Wait for the lock to be released
                System.Threading.Thread.Sleep(500);
            }
        }

        Log.LogTrace("WaitForFile {0} returning true after {1} tries",
            fullPath, numTries);
        return true;
    }
 

19voto

Simon Mourier Points 49585

Voici un code générique pour ce faire, indépendante de l'opération de fichier lui-même. C'est un exemple sur la façon de l'utiliser:

WrapSharingViolations(() => File.Delete(myFile));

ou

WrapSharingViolations(() => File.Copy(mySourceFile, myDestFile));

Vous pouvez également définir le nombre de tentatives, et le temps d'attente entre deux tentatives.

NOTE: Malheureusement, le sous-jacentes d'erreur Win32 (ERROR_SHARING_VIOLATION) n'est pas exposée .NET, donc j'ai ajouté un petit hack de la fonction (IsSharingViolation) basée sur la réflexion des mécanismes permettant de vérifier cela.

    /// <summary>
    /// Wraps sharing violations that could occur on a file IO operation.
    /// </summary>
    /// <param name="action">The action to execute. May not be null.</param>
    public static void WrapSharingViolations(WrapSharingViolationsCallback action)
    {
        WrapSharingViolations(action, null, 10, 100);
    }

    /// <summary>
    /// Wraps sharing violations that could occur on a file IO operation.
    /// </summary>
    /// <param name="action">The action to execute. May not be null.</param>
    /// <param name="exceptionsCallback">The exceptions callback. May be null.</param>
    /// <param name="retryCount">The retry count.</param>
    /// <param name="waitTime">The wait time in milliseconds.</param>
    public static void WrapSharingViolations(WrapSharingViolationsCallback action, WrapSharingViolationsExceptionsCallback exceptionsCallback, int retryCount, int waitTime)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        for (int i = 0; i < retryCount; i++)
        {
            try
            {
                action();
                return;
            }
            catch (IOException ioe)
            {
                if ((IsSharingViolation(ioe)) && (i < (retryCount - 1)))
                {
                    bool wait = true;
                    if (exceptionsCallback != null)
                    {
                        wait = exceptionsCallback(ioe, i, retryCount, waitTime);
                    }
                    if (wait)
                    {
                        System.Threading.Thread.Sleep(waitTime);
                    }
                }
                else
                {
                    throw;
                }
            }
        }
    }

    /// <summary>
    /// Defines a sharing violation wrapper delegate.
    /// </summary>
    public delegate void WrapSharingViolationsCallback();

    /// <summary>
    /// Defines a sharing violation wrapper delegate for handling exception.
    /// </summary>
    public delegate bool WrapSharingViolationsExceptionsCallback(IOException ioe, int retry, int retryCount, int waitTime);

    /// <summary>
    /// Determines whether the specified exception is a sharing violation exception.
    /// </summary>
    /// <param name="exception">The exception. May not be null.</param>
    /// <returns>
    ///     <c>true</c> if the specified exception is a sharing violation exception; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsSharingViolation(IOException exception)
    {
        if (exception == null)
            throw new ArgumentNullException("exception");

        int hr = GetHResult(exception, 0);
        return (hr == -2147024864); // 0x80070020 ERROR_SHARING_VIOLATION

    }

    /// <summary>
    /// Gets the HRESULT of the specified exception.
    /// </summary>
    /// <param name="exception">The exception to test. May not be null.</param>
    /// <param name="defaultValue">The default value in case of an error.</param>
    /// <returns>The HRESULT value.</returns>
    public static int GetHResult(IOException exception, int defaultValue)
    {
        if (exception == null)
            throw new ArgumentNullException("exception");

        try
        {
            const string name = "HResult";
            PropertyInfo pi = exception.GetType().GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance); // CLR2
            if (pi == null)
            {
                pi = exception.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance); // CLR4
            }
            if (pi != null)
                return (int)pi.GetValue(exception, null);
        }
        catch
        {
        }
        return defaultValue;
    }

15voto

user152791 Points 111

J'ai jeté une classe d'assistance pour ces sortes de choses. Il ne fonctionnera que si vous avez le contrôle sur tout ce qui permettrait d'accéder au fichier. Si vous attendez de la contention d'un tas d'autres choses, alors c'est assez inutile.

using System;
using System.IO;
using System.Threading;

/// <summary>
/// This is a wrapper aroung a FileStream.  While it is not a Stream itself, it can be cast to
/// one (keep in mind that this might throw an exception).
/// </summary>
public class SafeFileStream: IDisposable
{
    #region Private Members
    private Mutex m_mutex;
    private Stream m_stream;
    private string m_path;
    private FileMode m_fileMode;
    private FileAccess m_fileAccess;
    private FileShare m_fileShare;
    #endregion//Private Members

    #region Constructors
    public SafeFileStream(string path, FileMode mode, FileAccess access, FileShare share)
    {
    	m_mutex = new Mutex(false, String.Format("Global\\{0}", path.Replace('\\', '/')));
    	m_path = path;
    	m_fileMode = mode;
    	m_fileAccess = access;
    	m_fileShare = share;
    }
    #endregion//Constructors

    #region Properties
    public Stream UnderlyingStream
    {
    	get
    	{
    		if (!IsOpen)
    			throw new InvalidOperationException("The underlying stream does not exist - try opening this stream.");
    		return m_stream;
    	}
    }

    public bool IsOpen
    {
    	get { return m_stream != null; }
    }
    #endregion//Properties

    #region Functions
    /// <summary>
    /// Opens the stream when it is not locked.  If the file is locked, then
    /// </summary>
    public void Open()
    {
    	if (m_stream != null)
    		throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage);
    	m_mutex.WaitOne();
    	m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare);
    }

    public bool TryOpen(TimeSpan span)
    {
    	if (m_stream != null)
    		throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage);
    	if (m_mutex.WaitOne(span))
    	{
    		m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare);
    		return true;
    	}
    	else
    		return false;
    }

    public void Close()
    {
    	if (m_stream != null)
    	{
    		m_stream.Close();
    		m_stream = null;
    		m_mutex.ReleaseMutex();
    	}
    }

    public void Dispose()
    {
    	Close();
    	GC.SuppressFinalize(this);
    }

    public static explicit operator Stream(SafeFileStream sfs)
    {
    	return sfs.UnderlyingStream;
    }
    #endregion//Functions
}

Il fonctionne à l'aide d'un mutex nommé. Ceux qui souhaitent accéder au fichier tentative d'acquérir le contrôle de la mutex nommé, qui partage le nom du fichier (avec le '\'est transformé en'/'). Vous pouvez soit utiliser Open(), qui restera bloqué jusqu'à ce que le mutex est accessible ou vous pouvez utiliser TryOpen(TimeSpan), qui tente d'acquérir le mutex pour la durée donnée et renvoie false si elle ne peut pas acquérir à l'intérieur de l'intervalle de temps. Cela devrait probablement être utilisé à l'intérieur d'un bloc using, pour s'assurer que les verrous sont relâchés correctement, et le flux (si ouverte) seront éliminés correctement lorsque cet objet est supprimé.

J'ai fait un test rapide avec ~20 choses à faire différentes lectures/écritures du fichier et a vu la corruption. Évidemment, il n'est pas très avancé, mais il devrait fonctionner pour la majorité des cas simples.

5voto

jason saldo Points 5036

Pour cette application particulière observant directement le fichier va inévitablement conduire à un difficile de retrouver la trace de bug, surtout quand la taille du fichier augmente. Ici, sont deux stratégies différentes qui fonctionne.

  • Ftp deux fichiers, mais seulement regarder. Par exemple envoyer les fichiers important.txt et important.finition. Only watch pour la finale de fichier, mais le processus de txt.
  • FTP un fichier mais le renommer une fois terminé. Par exemple envoyer importante.attendre et faire l'expéditeur le renommer important.txt lorsque vous avez terminé.

Bonne chance!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X