共用方式為


異步磁碟 I/O 在 Windows 上顯示為同步

本文可協助您解決 I/O 的預設行為是同步的問題,但顯示為異步。

原始產品版本: Windows
原始 KB 編號: 156932

摘要

Microsoft Windows 上的檔案 I/O 可以是同步或異步的。 I/O 的預設行為是同步的,其中會呼叫 I/O 函式,並在 I/O 完成時傳回。 異步 I/O 可讓 I/O 函式立即將執行傳回給呼叫端,但 I/O 在一段時間之前不會假設完成。 操作系統會在 I/O 完成時通知呼叫端。 相反地,呼叫端可以使用操作系統的服務來判斷未完成 I/O 作業的狀態。

異步 I/O 的優點是呼叫端有時間在 I/O 作業完成時執行其他工作或發出更多要求。 重疊 I/O 一詞經常用於異步 I/O 和同步 I/O 的非重疊 I/O。 本文使用 I/O 作業的異步和同步詞彙。 本文假設讀者已熟悉檔案 I/O 函式,例如 CreateFileReadFileWriteFile

異步 I/O 作業通常就像同步 I/O 一樣。 本文會在後續各節中討論的某些條件,讓 I/O 作業以同步方式完成。 呼叫端沒有時間進行背景工作,因為 I/O 函式在 I/O 完成之前不會傳回。

數個函式與同步和異步 I/O 相關。 本文使用 ReadFileWriteFile 作為範例。 良好的替代機制是 ReadFileExWriteFileEx。 雖然本文只討論磁碟 I/O,但許多原則可以套用至其他類型的 I/O,例如序列 I/O 或網路 I/O。

設定異步 I/O

FILE_FLAG_OVERLAPPED開啟檔案時,必須在 中CreateFile指定 旗標。 此旗標可讓檔案上的 I/O 作業以異步方式完成。 以下是範例:

HANDLE hFile;

hFile = CreateFile(szFileName,
                      GENERIC_READ,
                      0,
                      NULL,
                      OPEN_EXISTING,
                      FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
                      NULL);

if (hFile == INVALID_HANDLE_VALUE)
      ErrorOpeningFile();

當您撰寫異步 I/O 的程式代碼時請小心,因為系統會保留在需要時同步作業的權利。 因此,如果您撰寫程式以正確處理可能以同步或異步方式完成的 I/O 作業,則最好這麼做。 範例程式代碼示範此考慮。

程式在等候異步操作完成時可以執行許多工作,例如佇列其他作業,或執行背景工作。 例如,下列程式代碼會正確處理讀取作業的重疊和非重疊完成。 它只不過等候未完成的 I/O 完成:

if (!ReadFile(hFile,
               pDataBuf,
               dwSizeOfBuffer,
               &NumberOfBytesRead,
               &osReadOperation )
{
   if (GetLastError() != ERROR_IO_PENDING)
   {
      // Some other error occurred while reading the file.
      ErrorReadingFile();
      ExitProcess(0);
   }
   else
      // Operation has been queued and
      // will complete in the future.
      fOverlapped = TRUE;
}
else
   // Operation has completed immediately.
   fOverlapped = FALSE;

if (fOverlapped)
{
   // Wait for the operation to complete before continuing.
   // You could do some background work if you wanted to.
   if (GetOverlappedResult( hFile,
                           &osReadOperation,
                           &NumberOfBytesTransferred,
                           TRUE))
      ReadHasCompleted(NumberOfBytesTransferred);
   else
      // Operation has completed, but it failed.
      ErrorReadingFile();
}
else
   ReadHasCompleted(NumberOfBytesRead);

注意

&NumberOfBytesRead傳入 ReadFile 與傳遞至 GetOverlappedResult不同&NumberOfBytesTransferred。 如果作業已進行異步處理,則會 GetOverlappedResult 用來判斷作業完成之後在作業中傳輸的實際位元組數目。 傳入ReadFile&NumberOfBytesRead 是毫無意義的。

另一方面,如果作業立即完成,則 &NumberOfBytesRead 傳入 ReadFile 的位元組數目是有效的。 在此情況下,請忽略傳入ReadFileOVERLAPPED 結構;請勿搭配 GetOverlappedResultWaitForSingleObject使用。

另一個具有異步操作的警告是,您必須在暫止作業完成之前,才使用 OVERLAPPED 結構。 換句話說,如果您有三個未處理的 I/O 作業,則必須使用三 OVERLAPPED 個結構。 如果您重複使用 OVERLAPPED 結構,將會在I/O作業中收到無法預期的結果,而且您可能會遇到數據損毀的情況。 此外,您必須正確初始化它,因此沒有任何剩餘的數據會影響新作業,才能第一次使用 OVERLAPPED 結構,或是在先前的作業完成之後重複使用它之前。

相同的限制類型適用於作業中使用的數據緩衝區。 在對應的 I/O 作業完成之前,數據緩衝區不得讀取或寫入;讀取或寫入緩衝區可能會導致錯誤和損毀的數據。

異步 I/O 仍顯示為同步

不過,如果您依照本文稍早的指示操作,所有 I/O 作業在發出的順序中仍通常會以同步方式完成,而且沒有任何ReadFile作業傳回 ERROR_IO_PENDINGFALSEGetLastError(),這表示您沒有任何時間進行任何背景工作。 為什麼會發生此情況?

即使您已針對異步操作撰寫程式代碼,I/O 作業仍會同步完成的原因有很多。

壓縮

異步操作的一個阻礙是新技術文件系統 (NTFS) 壓縮。 檔系統驅動程式不會以異步方式存取壓縮檔;而是將所有作業設為同步。 此阻礙不適用於使用類似 COMPRESS 或 PKZIP 之公用程式壓縮的檔案。

NTFS 加密

類似於壓縮,檔案加密會導致系統驅動程式將異步 I/O 轉換成同步。 如果檔案已解密,I/O 要求將會是異步的。

擴充檔案

I/O 作業同步完成的另一個原因是作業本身。 在 Windows 上,任何擴充其長度的檔案寫入作業都會是同步的。

注意

應用程式可以使用 函式來變更檔案 SetFileValidData 的有效數據長度,然後發出 WriteFile,以異步方式進行先前提及的寫入作業。

使用 SetFileValidData (可在 Windows XP 和更新版本上使用),應用程式可以有效率地擴充檔案,而不會造成零填滿檔案的效能損失。

由於 NTFS 文件系統不會以零填滿數據到所 SetFileValidData定義的有效數據長度 (VDL),因此此函式具有安全性影響,其中檔案可能由其他檔案佔用的叢集。 因此, SetFileValidData 要求呼叫端已啟用新的 SeManageVolumePrivilege 功能(根據預設,這隻會指派給系統管理員)。 Microsoft建議獨立軟體供應商(ISV)仔細考慮使用這類功能的影響。

Cache

大部分的 I/O 驅動程式(磁碟、通訊和其他驅動程式)都有特殊案例程式代碼,其中,如果可以立即完成 I/O 要求,作業將會完成,或 ReadFile WriteFile 函式會傳回 TRUE。 以各種方式,這些類型的作業看起來是同步的。 對於磁碟裝置,一般而言,在記憶體中快取數據時,可以立即完成 I/O 要求。

數據不在快取中

不過,如果數據不在快取中,快取配置可以針對您運作。 Windows 快取是使用檔案對應在內部實作。 Windows 中的記憶體管理員不提供異步頁面錯誤機制來管理快取管理員所使用的檔案對應。 快取管理員可以驗證要求的頁面是否在記憶體中,因此,如果您發出異步快取讀取,而且頁面不在記憶體中,文件系統驅動程式會假設您不希望封鎖線程,而且要求將由有限的背景工作線程集區處理。 在呼叫時,將控件傳回給程式 ReadFile ,且讀取仍擱置中。

這適用於少數要求,但因為背景工作線程集區有限(目前 16 MB 系統上有三個),因此在特定時間仍只會有少數排入磁碟驅動程式的要求。 如果您針對不在快取中的數據發出許多 I/O 作業,快取管理員和記憶體管理員就會變成飽和,而且您的要求會同步處理。

快取管理員的行為也可以根據您循序或隨機存取檔案而受到影響。 在循序存取檔案時,大部分都會看到快取的優點。 FILE_FLAG_SEQUENTIAL_SCAN呼叫中的CreateFile旗標會將這種存取類型的快取優化。 不過,如果您以隨機方式存取檔案,請使用 FILE_FLAG_RANDOM_ACCESS 中的 CreateFile 旗標,指示快取管理員優化其隨機存取行為。

請勿使用快取

FILE_FLAG_NO_BUFFERING 標對文件系統的行為影響最大,以進行異步操作。 這是保證 I/O 要求為異步的最佳方式。 它會指示文件系統完全不使用任何快取機制。

注意

使用此旗標有一些限制,這與數據緩衝區對齊和裝置的扇區大小相關。 如需詳細資訊,請參閱 CreateFile 函式文件中關於正確使用此旗標的函式參考。

真實世界測試結果

以下是範例程式代碼中的一些測試結果。 數字的規模在這裡並不重要,而且從計算機到計算機不等,但相較之下的數字關聯性照亮了旗標對效能的一般影響。

您可以預期會看到類似下列其中一項的結果:

  • 測試 1

    Asynchronous, unbuffered I/O:  asynchio /f*.dat /n
    Operations completed out of the order in which they were requested.
       500 requests queued in 0.224264 second.
       500 requests completed in 4.982481 seconds.
    

    這項測試示範先前提到的程式會快速發出 500 個 I/O 要求,而且有太多時間執行其他工作或發出更多要求。

  • 測試 2

    Synchronous, unbuffered I/O: asynchio /f*.dat /s /n
        Operations completed in the order issued.
        500 requests queued and completed in 4.495806 seconds.
    

    此測試示範此程式花了 4.495880 秒呼叫 ReadFile 來完成其作業,但測試 1 只花費了 0.224264 秒來發出相同的要求。 在測試 2 中,程式沒有任何額外的時間可以執行任何背景工作。

  • 測試 3

    Asynchronous, buffered I/O: asynchio /f*.dat
        Operations completed in the order issued.
        500 requests issued and completed in 0.251670 second.
    

    此測試示範快取的同步本質。 所有讀取都是在 0.251670 秒中發行和完成的。 換句話說,異步要求會以同步方式完成。 此測試也會示範數據在快取中時快取管理員的高效能。

  • 測試 4

    Synchronous, buffered I/O: asynchio /f*.dat /s
        Operations completed in the order issued.
        500 requests and completed in 0.217011 seconds.
    

    此測試示範與測試 3 中相同的結果。 從快取進行同步讀取的速度比快取的異步讀取快一點。 此測試也會示範數據在快取中時快取管理員的高效能。

結論

您可以決定哪一種方法是最佳方法,因為它全都取決於程式所執行的作業類型、大小和數目。

默認檔案存取而不指定任何特殊旗標 CreateFile 為同步和快取作業。

注意

您會在此模式中取得一些自動異步行為,因為檔案系統驅動程式會執行預測性異步讀取,以及異步延遲寫入已修改的數據。 雖然此行為不會讓應用程式的 I/O 異步,但它是絕大多數簡單應用程式的理想案例。

另一方面,如果您的應用程式並不簡單,您可能必須執行一些分析與效能監視來判斷最佳方法,類似於本文稍早所述的測試。 分析 或 WriteFile 函式所ReadFile花費的時間,然後將這段時間與實際 I/O 作業完成所花費的時間進行比較相當有用。 如果大部分時間都花在實際發出 I/O 中,則您的 I/O 會以同步方式完成。 不過,相較於 I/O 作業完成所需的時間,發出 I/O 要求所花費的時間相對較小,則會以異步方式處理您的作業。 本文稍早所述的範例程式代碼會使用 函 QueryPerformanceCounter 式來執行自己的內部分析。

效能監視有助於判斷程式使用磁碟和快取的效率。 追蹤 Cache 物件的任何性能計數器將會指出快取管理員的效能。 追蹤實體磁碟或邏輯磁碟物件的性能計數器將會指出磁碟系統的效能。

有數個公用程式有助於效能監視。 PerfMonDiskPerf 特別有用。 若要讓系統收集磁碟系統效能的數據,您必須先發出 DiskPerf 命令。 發出命令之後,您必須重新啟動系統以啟動資料收集。

參考資料

同步和異步 I/O