首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

路徑、檔案、目錄、I/O 常見操作總彙整 (三) — 非同步讀寫、臨時檔案、獨立儲存區、檔案系統監視

     編輯:關於.NET

前言

本系列第三篇聚焦進階 I/O 操作:非同步檔案存取 (避免 UI 凍結)、臨時檔案/隨機檔名產生、獨立儲存區 (Isolated Storage,無需檔案系統權限)、FileSystemWatcher 即時監視檔案變更。所有範例採用 using 區塊確保資源釋放,並說明效能調校關鍵參數。

一、非同步檔案讀寫 — FileStream BeginRead/EndRead

適用場景:大檔案讀取、網路磁碟、不想阻塞 UI 執行緒。.NET 4.5+ 建議改用 async/await (ReadAsync/WriteAsync),此處示範 APM 模式以相容 .NET 2.0/3.5。

非同步讀取封裝類別


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

public class AsyncFileReader : IDisposable
{
    private readonly FileStream _stream;
    private readonly byte[] _buffer;
    private readonly int _bufferSize;
    private readonly Action _onDataRead;
    private readonly Action _onError;
    private readonly Action _onCompleted;

    public AsyncFileReader(string filePath, int bufferSize, 
        Action onDataRead, Action onError, Action onCompleted)
    {
        _bufferSize = bufferSize;
        _buffer = new byte[bufferSize];
        _onDataRead = onDataRead;
        _onError = onError;
        _onCompleted = onCompleted;

        // FileOptions.Asynchronous 關鍵:啟用 I/O 完成埠 (IOCP)
        _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 
            bufferSize, FileOptions.Asynchronous);
    }

    public void Start()
    {
        try
        {
            _stream.BeginRead(_buffer, 0, _bufferSize, ReadCallback, null);
        }
        catch (Exception ex) { _onError(ex); }
    }

    private void ReadCallback(IAsyncResult ar)
    {
        try
        {
            int bytesRead = _stream.EndRead(ar);
            if (bytesRead > 0)
            {
                _onDataRead(_buffer, bytesRead);
                // 遞迴讀取下一區塊
                _stream.BeginRead(_buffer, 0, _bufferSize, ReadCallback, null);
            }
            else
            {
                _onCompleted();
            }
        }
        catch (Exception ex) { _onError(ex); }
    }

    public void Dispose() => _stream?.Dispose();
}

使用範例 (WinForm/UI 執行緒不阻塞)


var reader = new AsyncFileReader(@"C:\LargeFile.dat", 8192,
    onDataRead: (buffer, count) => 
    {
        // 處理資料區塊 (如寫入另一檔案、網路傳送、解析)
        // 注意:此回呼在 ThreadPool 執行緒,若更新 UI 需 Invoke/BeginInvoke
    },
    onError: ex => MessageBox.Show($"讀取錯誤: {ex.Message}"),
    onCompleted: () => Console.WriteLine("讀取完成"));

reader.Start();
// 主執行緒可繼續處理 UI 事件
// 結束時 reader.Dispose()

.NET 4.5+ 建議寫法 (async/await)


public async Task ProcessLargeFileAsync(string path)
{
    using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, true))
    {
        var buffer = new byte[8192];
        int read;
        while ((read = await fs.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            await ProcessChunkAsync(buffer, read);
        }
    }
}

二、臨時檔案與隨機檔名

方法行為回傳適用場景
Path.GetTempFileName()在 %TEMP% 建立實體 0 位元組檔案,保證唯一完整路徑需立即寫入、稍後刪除
Path.GetRandomFileName()產生隨機檔名 (如 "abc123.tmp"),不建立檔案檔名字串需自行決定路徑、避免衝突
Path.GetTempPath()取得目前使用者臨時目錄路徑目錄路徑自行組合檔名

臨時檔案完整範例


string tempFile = Path.GetTempFileName(); // 例: C:\Users\John\AppData\Local\Temp\tmp1234.tmp
try
{
    using (var fs = new FileStream(tempFile, FileMode.Open, FileAccess.Write))
    using (var bw = new BinaryWriter(fs))
    {
        bw.Write("臨時資料內容");
        bw.Write(DateTime.Now.Ticks);
    }
    // 呼叫外部程式處理、上傳、列印等
    Process.Start("notepad.exe", tempFile);
}
finally
{
    if (File.Exists(tempFile)) File.Delete(tempFile); // 確保清理
}

三、獨立儲存區 — IsolatedStorage

適用場景:ClickOnce 應用、網頁應用、受限權限環境 (無 FileIOPermission)、使用者層級資料隔離 (不需管理路徑)。

核心類別

  • IsolatedStorageFile:虛擬檔案系統根目錄 (User/Assembly/Domain/Roaming 範圍)
  • IsolatedStorageFileStream:衍生自 FileStream,用法相同

完整範例:使用者/組件層級儲存設定


using System.IO;
using System.IO.IsolatedStorage;

public class UserSettingsStore
{
    private const string FileName = "settings.dat";
    private const string DirName = "MyApp";

    public void Save(SettingsData data)
    {
        // 取得「目前使用者 + 目前組件」的獨立存放區
        using (var store = IsolatedStorageFile.GetUserStoreForAssembly())
        {
            if (!store.DirectoryExists(DirName)) store.CreateDirectory(DirName);
            
            using (var stream = new IsolatedStorageFileStream(
                Path.Combine(DirName, FileName), FileMode.Create, store))
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(data.Version);
                writer.Write(data.LastLogin.Ticks);
                writer.Write(data.PreferencesJson);
            }
        }
    }

    public SettingsData Load()
    {
        using (var store = IsolatedStorageFile.GetUserStoreForAssembly())
        {
            string path = Path.Combine(DirName, FileName);
            if (!store.FileExists(path)) return new SettingsData(); // 預設值
            
            using (var stream = new IsolatedStorageFileStream(path, FileMode.Open, store))
            using (var reader = new BinaryReader(stream))
            {
                return new SettingsData
                {
                    Version = reader.ReadInt32(),
                    LastLogin = new DateTime(reader.ReadInt64()),
                    PreferencesJson = reader.ReadString()
                };
            }
        }
    }

    public long GetUsedSize()
    {
        using (var store = IsolatedStorageFile.GetUserStoreForAssembly())
            return store.CurrentSize; // 位元組
    }
}

範圍選擇指南

方法範圍隔離層級典型用途
GetUserStoreForAssembly()User + Assembly同一使用者、同一組件 (強名稱/URL 證據)應用程式設定、快取
GetUserStoreForDomain()User + AppDomain同一使用者、同一應用程式域插件/增益集隔離資料
GetMachineStoreForAssembly()Machine + Assembly全機器、同一組件共用授權檔、系統級快取
GetStore(IsolatedStorageScope.User | Assembly, ...)自訂組合依證據類型 (StrongName, Url, Publisher)精細控制

四、檔案系統監視 — FileSystemWatcher

即時偵測目錄/檔案 建立、刪除、變更、重命名。注意:事件在 ThreadPool 執行緒引發,更新 UI 需跨執行緒封送。

完整監視元件封裝


using System;
using System.IO;
using System.Windows.Forms; // 假設 WinForm

public class DirectoryMonitor : IDisposable
{
    private readonly FileSystemWatcher _watcher;
    private readonly Action _onChanged;

    public DirectoryMonitor(string path, string filter, Action onChanged)
    {
        _onChanged = onChanged;
        _watcher = new FileSystemWatcher(path, filter)
        {
            IncludeSubdirectories = true,
            NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirName 
                         | NotifyFilters.LastWrite | NotifyFilters.Size
                         | NotifyFilters.Attributes | NotifyFilters.Security
        };

        _watcher.Created += OnEvent;
        _watcher.Deleted += OnEvent;
        _watcher.Changed += OnEvent;
        _watcher.Renamed += (s, e) => _onChanged($"{e.OldFullPath} → {e.FullPath}", WatcherChangeTypes.Renamed);
        
        _watcher.Error += (s, e) => 
        {
            // 緩衝區溢位時重置
            if (e.GetException() is InternalBufferOverflowException)
            {
                _watcher.EnableRaisingEvents = false;
                _watcher.EnableRaisingEvents = true;
            }
        };
    }

    private void OnEvent(object sender, FileSystemEventArgs e)
    {
        _onChanged(e.FullPath, e.ChangeType);
    }

    public void Start() => _watcher.EnableRaisingEvents = true;
    public void Stop() => _watcher.EnableRaisingEvents = false;

    public void Dispose() 
    {
        Stop();
        _watcher.Dispose();
    }
}

WinForm 整合範例 (跨執行緒更新 UI)


public partial class MainForm : Form
{
    private DirectoryMonitor _monitor;

    private void btnStartMonitor_Click(object sender, EventArgs e)
    {
        _monitor = new DirectoryMonitor(@"C:\WatchDir", "*.txt", OnFileChanged);
        _monitor.Start();
    }

    private void OnFileChanged(string path, WatcherChangeTypes type)
    {
        // 必須 Invoke 回 UI 執行緒
        if (InvokeRequired)
        {
            BeginInvoke(new Action(OnFileChanged), path, type);
            return;
        }
        lstLog.Items.Add($"[{DateTime.Now:HH:mm:ss}] {type}: {path}");
        lstLog.TopIndex = lstLog.Items.Count - 1;
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _monitor?.Dispose();
        base.OnFormClosing(e);
    }
}

關鍵屬性與避坑指南

屬性/事件說明注意事項
NotifyFilter指定監視哪些變更類型 (Flags)預設只含 FileName/DirName/LastWrite,**務必加入 Size/Attributes** 否則修改檔案內容不觸發 Changed
IncludeSubdirectories是否遞迴監視子目錄true 會大幅增加事件數量,注意效能
InternalBufferOverflowException事件產生速度超過處理速度增大 InternalBufferSize (預設 8KB, 最大 64KB)、簡化事件處理邏輯、啟用 IncludeSubdirectories=false
Changed 事件重複觸發某些編輯器 (如 Notepad) 存檔會觸發多次應用去抖動邏輯 (如 500ms 內同路徑同類型只處理一次)
網路磁碟監視SMB 共享支援有限需遠端主機啟用 "File Change Notification" 服務,不穩定建議改輪詢

五、讀寫檔案核心類別速查表

類別基底/介面資料型態編碼支援典型用途
FileStreamStreamByte[]無 (原始位元組)所有檔案 I/O 基礎、非同步、隨機存取
StreamReader/WriterTextReader/WriterString/CharUTF-8 (預設)、Unicode、ASCII、自訂文字檔逐行讀寫、CSV、Log、JSON
BinaryReader/WriterObjectPrimitive TypesUTF-7 (字串)二進位格式、自訂協定、高效序列化
StringReader/WriterTextReader/WriterString (記憶體)UTF-16 (內部)字串模擬檔案、單元測試、記憶體緩衝
MemoryStreamStreamByte[] (記憶體)無暫存資料、序列化中介、圖片處理
BufferedStreamStreamByte[]無包裝非緩衝 Stream (如 NetworkStream) 提升效能
GZipStream/DeflateStreamStreamByte[]無串流壓縮/解壓縮
CryptoStreamStreamByte[]無串流加密/解密

總結:選型決策樹

需要非同步不阻塞 UI? ──是──→ .NET 4.5+ 用 async/await (ReadAsync/WriteAsync)
                                    .NET 2.0/3.5 用 FileStream.BeginRead/EndRead (IOCP)
                                    │
需要無權限寫入使用者資料? ──是──→ IsolatedStorage (GetUserStoreForAssembly)
                                    │
需要即時偵測檔案變更? ──是──→ FileSystemWatcher (注意緩衝區溢位、去抖動)
                                    │
需要臨時檔案自動清理? ──是──→ Path.GetTempFileName() + finally File.Delete()
                                    │
一般文字檔讀寫 ──→ StreamReader/Writer (指定 Encoding)
一般二進位讀寫 ──→ BinaryReader/Writer
大檔案/隨機存取 ──→ FileStream (Position/Seek)
記憶體高速緩衝 ──→ MemoryStream
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved