如何在 C# winform 应用程序中执行非缓存文件写入

本文关键字:执行 缓存文件 应用程序 winform | 更新日期: 2023-09-27 17:55:26

我正在尝试确定最坏情况下的磁盘速度,所以我编写了以下函数。

static public decimal MBytesPerSec(string volume)
{
    string filename = volume + "''writetest.tmp";
    if (System.IO.File.Exists(filename))
        System.IO.File.Delete(filename);
    System.IO.StreamWriter file = new System.IO.StreamWriter(filename);
    char[] data = new char[64000];
    Stopwatch watch = new Stopwatch();
    watch.Start();
    int i = 0;
    for (; i < 1000; i++)
    {
        file.Write(data);
        if (watch.ElapsedMilliseconds > 2000)
        {
            break;
        }
    }
    watch.Stop();
    file.Close();
    System.IO.File.Delete(volume + "''test.txt");
    decimal mbytessec = (i * 64 / watch.ElapsedMilliseconds);
    return mbytessec;
}

该函数工作正常,但写入被缓存,因此速度不是最坏的情况。

在 WIN32 C++中,我只需使用 FILE_FLAG_NO_BUFFERINGFILE_FLAG_WRITE_THROUGH 选项创建文件,然后确保遵循非缓存写入规则(以扇区大小偏移量写入文件,最少写入 4k)

我找到了一篇讨论 .NET 技术的文章。

所以我写了一个新函数(忽略数学错误)。

static public decimal MBytesPerSecNonCached(string volume)
{
    const FileOptions FILE_FLAG_NO_BUFFERING = (FileOptions)0x20000000;
    string filename = volume + "''writetest.tmp";
    using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 1024, FileOptions.WriteThrough | FILE_FLAG_NO_BUFFERING))
    {
        byte[] data = new byte[65535];
        int i = 0;
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (; i < 1000; i++)
        {
            fs.Write(data, 0, 65535);
            if (watch.ElapsedMilliseconds > 2000)
            {
                break;
            }
        }
        watch.Stop();
        fs.Close();
        System.IO.File.Delete(filename);
        decimal mbytessec = (i * 64 / watch.ElapsedMilliseconds);
        return mbytessec;
    }
}

此函数适用于 4k、16K 和 32K 写入大小,但是一旦我尝试 64K 写入大小,我就会得到一个异常:

IO 操作将不起作用。文件很可能变得太长,或者句柄未打开以支持同步 IO 操作。

那么,如何解决此问题,以便可以使用大于 32KB 的写入大小(64KB 到 4096KB)进行测试?

如何在 C# winform 应用程序中执行非缓存文件写入

尝试一些非托管代码:

[DllImport("kernel32", SetLastError = true)]
        static extern unsafe SafeFileHandle CreateFile(
            string FileName,           // file name
            uint DesiredAccess,        // access mode
            uint ShareMode,            // share mode
            IntPtr SecurityAttributes, // Security Attr
            uint CreationDisposition,  // how to create
            uint FlagsAndAttributes,   // file attributes
            IntPtr hTemplate // template file  
            );
const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
SafeFileHandle handle = CreateFile("filename",
                            (uint)FileAccess.Write,
                            (uint)FileShare.None,
                            IntPtr.Zero,
                            (uint)FileMode.Open,
                             FILE_FLAG_NO_BUFFERING,
                            IntPtr.Zero);
var unBufferedStream = new FileStream(handle,FileAccess.Read,blockSize,false);

现在,您应该可以访问一个未缓冲的流,您可以随意读写,没有任何限制

为了记录在案....您还可以像这样禁用缓存:

[DllImport("KERNEL32", SetLastError = true)]
        public extern static int DeviceIoControl(IntPtr hDevice, uint IoControlCode,
            IntPtr lpInBuffer, uint InBufferSize,
            IntPtr lpOutBuffer, uint nOutBufferSize,
            ref uint lpBytesReturned,
            IntPtr lpOverlapped);
        [DllImport("KERNEL32", SetLastError = true)]
        public extern static int CloseHandle(
        IntPtr hObject);
[StructLayout(LayoutKind.Sequential)]
        public struct DISK_CACHE_INFORMATION
        {            
            public byte ParametersSavable;            
            public byte ReadCacheEnabled;            
            public byte WriteCacheEnabled;
            public int ReadRetentionPriority;//DISK_CACHE_RETENTION_PRIORITY = enum = int
            public int WriteRetentionPriority;//DISK_CACHE_RETENTION_PRIORITY = enum = int
            public Int16 DisablePrefetchTransferLength;//WORD            
            public byte PrefetchScalar;            
        }
public void SetDiskCache(byte val)
        {
            IntPtr h = CreateFile("''''.''PHYSICALDRIVE0", (uint)FileAccess.Read | (uint)FileAccess.Write, (uint)FileShare.Write, IntPtr.Zero, (uint)FileMode.Open, 0, IntPtr.Zero);
            DISK_CACHE_INFORMATION sInfo = new DISK_CACHE_INFORMATION();
            IntPtr ptrout = Marshal.AllocHGlobal(Marshal.SizeOf(sInfo));
            Marshal.StructureToPtr(sInfo, ptrout, true);            
            uint dwWritten = 0;
            int ret = DeviceIoControl(h,IOCTL_DISK_GET_CACHE_INFORMATION,IntPtr.Zero,0,ptrout,(uint)Marshal.SizeOf(sInfo),ref dwWritten,IntPtr.Zero);            
            sInfo = (DISK_CACHE_INFORMATION)Marshal.PtrToStructure(ptrout,typeof(DISK_CACHE_INFORMATION));            
            sInfo.ReadCacheEnabled = val;
            // acuma trimite structura modificata
            IntPtr ptrin = Marshal.AllocHGlobal(Marshal.SizeOf(sInfo));
            Marshal.StructureToPtr(sInfo, ptrin, true);            
            ret = DeviceIoControl(h, IOCTL_DISK_SET_CACHE_INFORMATION, ptrin, (uint)Marshal.SizeOf(sInfo), IntPtr.Zero, 0, ref dwWritten, IntPtr.Zero);            
            CloseHandle(h);            
        }