我使用 Get-WmiObject Win32_DiskDrive
命令在 PowerShell 裡面呼叫 WMI 讀取硬碟資訊,可以看到如下輸出內容
Get-WmiObject Win32_DiskDrive
Partitions : 1
DeviceID : \\.\PHYSICALDRIVE1
Model : QEMU QEMU HARDDISK SCSI Disk Device
Size : 105226007040
Caption : QEMU QEMU HARDDISK SCSI Disk DevicePartitions : 2
DeviceID : \\.\PHYSICALDRIVE0
Model : QEMU QEMU HARDDISK SCSI Disk Device
Size : 118106795520
Caption : QEMU QEMU HARDDISK SCSI Disk DevicePartitions : 1
DeviceID : \\.\PHYSICALDRIVE2
Model : QEMU QEMU HARDDISK SCSI Disk Device
Size : 118106795520
Caption : QEMU QEMU HARDDISK SCSI Disk Device
透過裝置管理器能看到如下介面
經過 lsj 的分析,預計是虛擬磁碟的問題,導致移動成功了,但是 Win32 給出錯誤資訊
為了更好復現問題,我編寫了以下測試程式。經過我的測試,這個問題和 WPF 無關,且和 dotnet 無關,和網路下載無關。僅僅只是以下簡單的多個檔案同時寫入,寫入之後進行移動,就能復現問題
var workFolder = Path.Join(Path.GetTempPath(), "Test-RecedajeeKewhinuhoyay");
Directory.CreateDirectory(workFolder);
Console.WriteLine($"測試資料夾 {workFolder}");
var locker = new object();
var taskList = new List<Task>();
for (int i = 0; i < 50; i++)
{
var task = Task.Run(async () =>
{
var fileName = Path.GetRandomFileName();
var filePath = Path.Join(workFolder, fileName + ".tmp");
await MockDownloadAsync(filePath);
var newFilePath = Path.Join(workFolder, fileName);
try
{
File.Move(filePath, newFilePath);
}
catch (Exception e)
{
/*
Move file fail. FilePath=C:\Users\admin\AppData\Local\Temp\Test-RecedajeeKewhinuhoyay\zvxau5gx.lmz.tmp. HResult=80070003;System.IO.DirectoryNotFoundException: Could not find a part of the path.
at System.IO.FileSystem.MoveFile(String, String, Boolean)
at System.IO.File.Move(String, String, Boolean)
at System.IO.File.Move(String, String)
at Program.<>c__DisplayClass0_0.<<<Main>$>b__2>d.MoveNext()
*/
// 新檔案存在: True;原檔案存在: False
if (e is IOException ioException)
{
Output($"Move file fail. FilePath={filePath}. HResult={ioException.HResult:X};\r\n新檔案存在: {File.Exists(newFilePath)};原檔案存在: {File.Exists(filePath)}\r\n{ioException}");
}
else
{
Output($"Move file fail. FilePath={filePath}.\r\n新檔案存在: {File.Exists(newFilePath)};原檔案存在: {File.Exists(filePath)}\r\n{e}");
}
}
});
taskList.Add(task);
}
void Output(string message)
{
Console.WriteLine(message);
}
async Task MockDownloadAsync(string filePath)
{
using var fileStream = File.OpenWrite(filePath);
var buffer = new byte[10240];
for (int i = 0; i < 100; i++)
{
Random.Shared.NextBytes(buffer);
fileStream.Write(buffer, 0, buffer.Length);
await Task.Delay(15);
}
}
Task.WaitAll(taskList);
Console.WriteLine("執行完成");
以上程式碼使用 MockDownloadAsync 方法代替真實的網路下載。同時執行多個檔案的寫入,似乎能夠讓虛擬磁碟更加忙碌,於是就能夠復現問題
丟擲的異常如下
System.IO.DirectoryNotFoundException: Could not find a part of the path.
at System.IO.FileSystem.MoveFile(String, String, Boolean)
at System.IO.File.Move(String, String, Boolean)
at System.IO.File.Move(String, String)
at Program.<>c__DisplayClass0_0.<<<Main>$>b__2>d.MoveNext()
對應的 Win32 錯誤碼是 0x80070003
本文程式碼放在 github 和 gitee 上,可以使用如下命令列拉取程式碼。我整個程式碼倉庫比較龐大,使用以下命令列可以進行部分拉取,拉取速度比較快
先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin a3b5ce1d8dcc08cdc2d23e436e6fb477f1fac503
以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼。如果依然拉取不到程式碼,可以發郵件向我要程式碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin a3b5ce1d8dcc08cdc2d23e436e6fb477f1fac503
獲取程式碼之後,進入 Workbench/RecedajeeKewhinuhoyay 資料夾,即可獲取到原始碼
更多技術部落格,請參閱 部落格導航