112

ファイルのロックが解除され、読み取りや名前の変更ができるようになるまで、スレッドをブロックする最も簡単な方法は何ですか? たとえば、.NET Framework のどこかに WaitOnFile() はありますか?

FileSystemWatcher を使用して FTP サイトに送信されるファイルを検索するサービスがありますが、他のプロセスがファイルの書き込みを完了する前にファイル作成イベントが発生します。

理想的な解決策は、タイムアウト期間を設けて、スレッドが放棄する前に永遠にハングしないようにすることです。

編集:以下の解決策のいくつかを試した後、システムを変更して、すべてのファイルが に書き込み、最終的な場所にPath.GetTempFileName()a を実行するようにしました。File.Move()イベントが発生するとすぐにFileSystemWatcher、ファイルはすでに完成していました。

4

16 に答える 16

84

エリックの答えから始めて、コードをはるかにコンパクトで再利用できるようにするためのいくつかの改善を含めました。お役に立てば幸いです。

FileStream WaitForFile (string fullPath, FileMode mode, FileAccess access, FileShare share)
{
    for (int numTries = 0; numTries < 10; numTries++) {
        FileStream fs = null;
        try {
            fs = new FileStream (fullPath, mode, access, share);
            return fs;
        }
        catch (IOException) {
            if (fs != null) {
                fs.Dispose ();
            }
            Thread.Sleep (50);
        }
    }

    return null;
}
于 2010-09-09T15:19:00.653 に答える
45

これは私が関連する質問にした答えでした:

    /// <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;
    }
于 2008-09-08T21:59:11.790 に答える
19

ファイル操作自体から独立して、これを行うための一般的なコードを次に示します。これは、使用方法の例です。

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

また

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

また、再試行回数と再試行間の待機時間を定義することもできます。

注: 残念ながら、根本的な Win32 エラー (ERROR_SHARING_VIOLATION) は .NET では公開されていないため、これIsSharingViolationを確認するためにリフレクション メカニズムに基づく小さなハック関数 ( ) を追加しました。

    /// <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;
    }
于 2011-03-26T10:12:45.600 に答える
15

これらの種類のヘルパー クラスをまとめました。ファイルにアクセスするすべてのものを制御できる場合は機能します。他の多くのものか​​らの競合を期待している場合、これはまったく価値がありません。

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
}

名前付きミューテックスを使用して動作します。ファイルにアクセスしたい人は、ファイルの名前を共有する名前付きミューテックスの制御を取得しようとします (「\」が「/」に変わります)。ミューテックスにアクセスできるようになるまで停止する Open() を使用するか、TryOpen(TimeSpan) を使用して、指定された期間ミューテックスを取得しようとし、時間内に取得できない場合は false を返します。これは、ロックが適切に解放され、このオブジェクトが破棄されたときにストリーム (開いている場合) が適切に破棄されるようにするために、おそらく using ブロック内で使用する必要があります。

ファイルのさまざまな読み取り/書き込みを行うために、約 20 のことを行って簡単なテストを行いましたが、破損は見られませんでした。明らかに、それはあまり高度ではありませんが、ほとんどの単純なケースで機能するはずです。

于 2009-08-07T22:40:56.717 に答える
5

この特定のアプリケーションの場合、ファイルを直接観察すると、特にファイル サイズが大きくなると、必然的に追跡が困難なバグが発生します。ここでは、機能する 2 つの異なる戦略を示します。

  • 2 つのファイルを ftp しますが、監視するのは 1 つだけです。たとえば、ファイル important.txt と important.finish を送信します。終了ファイルのみを監視し、txt を処理します。
  • 1 つのファイルを FTP で転送しますが、完了したら名前を変更します。たとえば、important.wait を送信し、終了時に送信者にその名前を important.txt に変更してもらいます。

幸運を!

于 2008-09-09T00:20:33.183 に答える
4

少し前に使用した手法の 1 つは、独自の関数を作成することでした。基本的に例外をキャッチし、指定された期間起動できるタイマーを使用して再試行します。より良い方法があれば、共有してください。

于 2008-09-08T21:37:55.727 に答える
3

MSDNから:

OnCreated イベントは、ファイルが作成されるとすぐに発生します。ファイルが監視対象のディレクトリにコピーまたは転送されている場合、OnCreated イベントがすぐに発生し、その後に 1 つ以上の OnChanged イベントが発生します。

FileSystemWatcher は、「OnCreated」イベント中に読み取り/名前変更を行わないように変更できますが、次のようになります。

  1. (FileInfo オブジェクトを使用して) ロックされなくなるまで、ファイルの状態をポーリングするスレッドをスパンします。
  2. ファイルのロックが解除され、準備ができていると判断されるとすぐに、サービスを呼び出してファイルを処理します。
于 2008-09-08T21:48:11.973 に答える
2

ほとんどの場合、@harpoが提案するような単純なアプローチが機能します。このアプローチを使用して、より洗練されたコードを開発できます。

  • SystemHandleInformation \ SystemProcessInformationを使用して、選択したファイルに対して開いているすべてのハンドルを検索します
  • 内部ハンドルにアクセスするためのサブクラスWaitHandleクラス
  • サブクラス化されたWaitHandleでラップされた見つかったハンドルをWaitHandle.WaitAnyメソッドに渡します
于 2008-09-09T00:11:30.147 に答える
2

ファイル送信完了後に作成される転送処理トリガーファイル SameNameASTrasferedFile.trg への広告。

次に、*.trg ファイルでのみイベントを発生させる FileSystemWatcher をセットアップします。

于 2011-07-08T15:10:18.290 に答える
1

ファイルのロック状態を判断するために何を使用しているかはわかりませんが、このような方法で行う必要があります。

while (真)
{
    試す {
        stream = File.Open( ファイル名, ファイルモード );
        壊す;
    }
    キャッチ(FileIOException){

        // ロックの問題かどうかをチェック

        Thread.Sleep(100);
    }
}
于 2008-09-08T21:42:27.290 に答える
0

ファイルが存在するかどうかを確認するチェックを追加したことを除いて、上記と同様の回答があります。

bool WaitForFile(string fullPath)
        {
            int numTries = 0;
            while (true)
            {
                //need to add this line to prevent infinite loop
                if (!File.Exists(fullPath))
                {
                    _logger.LogInformation("WaitForFile {0} returning true - file does not exist", fullPath);
                    break;
                }
                ++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)
                {
                    _logger.LogInformation(
                       "WaitForFile {0} failed to get an exclusive lock: {1}",
                        fullPath, ex.ToString());

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

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

            _logger.LogInformation("WaitForFile {0} returning true after {1} tries",
                fullPath, numTries);
            return true;
        }
于 2021-05-06T19:21:25.217 に答える
-1

NotifyFilter NotifyFilters.LastWriteでChangedイベントを使用するだけです:

var watcher = new FileSystemWatcher {
      Path = @"c:\temp\test",
      Filter = "*.xml",
      NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += watcher_Changed; 
watcher.EnableRaisingEvents = true;
于 2013-01-22T20:51:29.733 に答える
-1

私は Gulzar と同じように、ループを試し続けます。

実際、私はファイル システム ウォッチャーを気にすることさえありません。1 分に 1 回ネットワーク ドライブをポーリングして新しいファイルを探すのは安価です。

于 2008-09-08T23:14:58.567 に答える
-1

Outlook の添付ファイルを追加するときに、同様の問題に遭遇しました。「使用」はその日を救った。

string fileName = MessagingBLL.BuildPropertyAttachmentFileName(currProp);

                //create a temporary file to send as the attachment
                string pathString = Path.Combine(Path.GetTempPath(), fileName);

                //dirty trick to make sure locks are released on the file.
                using (System.IO.File.Create(pathString)) { }

                mailItem.Subject = MessagingBLL.PropertyAttachmentSubject;
                mailItem.Attachments.Add(pathString, Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing);
于 2014-03-04T16:58:04.827 に答える
-3

オプションとしてこれはどうですか:

private void WaitOnFile(string fileName)
{
    FileInfo fileInfo = new FileInfo(fileName);
    for (long size = -1; size != fileInfo.Length; fileInfo.Refresh())
    {
        size = fileInfo.Length;
        System.Threading.Thread.Sleep(1000);
    }
}

もちろん、作成時にファイルサイズが事前に割り当てられている場合、誤検知が発生します。

于 2009-07-23T12:56:35.410 に答える