簡體   English   中英

FileStream 和 C# 中的 FileSystemWatcher,奇怪的問題“進程無法訪問文件”

[英]FileStream and a FileSystemWatcher in C#, Weird Issue “process cannot access the file”

我有這個復雜的代碼庫,它正在偵聽某個文件夾上的 FileCreated 事件。 創建文件后(還包括將文件移動到該文件夾​​),我想讀入該文件並對其進行處理。 它適用於第一個文件,但在所有其他嘗試之后拋出異常。 在調試模式(使用 VisualStudio)中,錯誤會被拋出,但如果我只是點擊“繼續”......它就會工作(沒有錯誤)。

我已經發布了簡化的代碼,它演示了這個問題。

例如,您啟動應用程序,單擊“開始”按鈕,然后“創建新的文本文件”

輸出是:

Working

如果您隨后以完全相同的方式創建第二個文件,則輸出為:

Broken: The process cannot access the file 'C:\TestFolder\New Text Document (2).txt' because it is being used by another process.
Working, after breaking

查看我的代碼后,您將看到上面的打印輸出集意味着首先拋出了“無法訪問文件”異常,但是在 catch 語句中執行相同的調用突然起作用了。

這對我來說沒有任何意義,因為該文件顯然沒有被其他任何東西使用(我剛剛創建了它)……無論如何它一秒鍾后就可以工作了……

下面是我的代碼

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" >
    <StackPanel>
        <Button Click="Button_Click"  Content="Start"/>
    </StackPanel>
</Window>

背后的代碼:

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;


namespace WpfApplication1
{ 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            test();
        }


        String Folder = @"C:\TestFolder";

        private void test()
        {
            FileSystemWatcher watch = new FileSystemWatcher(Folder);
            watch.Created += new FileSystemEventHandler(FileCreated);
            watch.EnableRaisingEvents = true;

            Process.Start(Folder);
        }

        private void FileCreated(object sender, FileSystemEventArgs fsEvent)
        {

            if (File.Exists(fsEvent.FullPath))
            {

                // Thread.Sleep(1000);// Sleeping for 1 second seems to prevent the error from happening...?
                // If i am debugging, and pause on exceptions... then it also suddenly works (similar to the Sleep above)
                try
                {

                    FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open); 
                    Console.WriteLine("Working");
                    fs.Close();
                }
                catch (IOException ex)
                {
                    Console.WriteLine("Broken: " + ex.Message);
                    try
                    {                        
                        FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open);
                        Console.WriteLine("Working, after breaking");
                        fs.Close();

                    }
                    catch(IOException ex2)
                    {                        
                        FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open);
                        Console.WriteLine("really broken: " + ex2.Message);
                        fs.Close();
                    }
                }


            }
        }
    }
}

我已經看到了自 .NET 1.0 以來您描述的行為,並且從未費心找出它發生的原因。 在您調用 close 和 dispose 之后,似乎操作系統或 .NET 有時(?)會在短時間內鎖定文件。

我做了一個變通方法 - 如果你喜歡,或者 hack - 已經證明對我們來說非常強大。 我們每天在服務器場中處理數百萬個文件,文件觀察者檢測到的所有文件都通過此方法,然后再將其移交給進一步處理。

它的作用是在文件上放置一個排他鎖。 如果失敗,它會選擇等待 10 秒讓文件關閉,然后再放棄。

    public static bool IsFileClosed(string filepath, bool wait)
    {
        bool        fileClosed = false;
        int         retries = 20;
        const int   delay = 500; // Max time spent here = retries*delay milliseconds

        if (!File.Exists(filepath))
            return false;

        do
        {
            try 
            {
                // Attempts to open then close the file in RW mode, denying other users to place any locks.
                FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                fs.Close();
                fileClosed = true; // success
            }
            catch (IOException) {}

            if (!wait) break;

            retries --;

            if (!fileClosed)
                Thread.Sleep( delay );
        }
        while (!fileClosed && retries > 0);

        return fileClosed;
    }

最有可能發生的情況是FileCreated事件被引發並嘗試在文件完全寫入磁盤之前處理文件。

有關避免此問題的技術,請參閱等待文件完全寫入

嘗試禁用任何防病毒/反惡意軟件。 默認情況下,大多數配置為在創建時掃描文件。

Ty 到 EventHorizo​​n 上面的代碼,很好地滿足了我的需求,並且似乎捕捉到了 Brendan 共享鏈接中的本質。

將其稍微修改為異步返回元組(既用於在 do/while 結束時取回文件鎖定狀態,也用於傳遞 ms 以查看發生了哪種退出)。 我唯一不舒服的是,我不確定為什么會這樣,即使將延遲降低到 2 毫秒,我仍然無法觸發 catch IOException 條件(超過一個 do 循環的證據) ) 所以我對 IO 故障情況沒有任何直接可見性,其他人可能有更大的文件,他們可以通過以下方式驗證:

public async Task<(bool, int)> IsFileClosed(string filepath)
{
  bool fileClosed = false;
  int baseretries = 40;
  int retries = 40;
  const int delay = 250;

  if (!File.Exists(filepath))
    return (false,0);

  Task<bool> FileCheck = Task.Run(() =>
  {
    do
    {
      try
      {
        FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
        fs.Close();
        fileClosed = true;
      }
      catch (IOException) { }
      retries--;

      if (!fileClosed)
        Thread.Sleep(delay);
    }
    while (!fileClosed && retries > 0);
    return fileClosed;
  });

  fileClosed = await FileCheck;
  return (fileClosed, (baseretries - retries) * delay);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM