Operacje we/wy dysku asynchronicznego są wyświetlane jako synchroniczne w systemie Windows

Ten artykuł pomaga rozwiązać problem polegający na tym, że domyślne zachowanie operacji we/wy jest synchroniczne, ale jest wyświetlane jako asynchroniczne.

Oryginalna wersja produktu: Windows
Oryginalny numer KB: 156932

Podsumowanie

Operacje we/wy plików w systemie Microsoft Windows mogą być synchroniczne lub asynchroniczne. Domyślne zachowanie operacji we/wy jest synchroniczne, gdzie jest wywoływana funkcja we/wy i zwracana po zakończeniu operacji we/wy. Asynchroniczne operacje we/wy umożliwiają funkcji we/wy natychmiastowe zwrócenie wykonania do obiektu wywołującego, ale zakłada się, że operacje we/wy zostaną ukończone dopiero w przyszłości. System operacyjny powiadamia obiekt wywołujący po zakończeniu operacji we/wy. Zamiast tego obiekt wywołujący może określić stan zaległej operacji we/wy przy użyciu usług systemu operacyjnego.

Zaletą asynchronicznego we/wy jest to, że obiekt wywołujący ma czas na wykonanie innej pracy lub wysłanie większej liczby żądań podczas wykonywania operacji we/wy. Termin Nakładające się operacje we/wy jest często używany w przypadku asynchronicznych operacji we/wy i nie nakładających się operacji we/wy na synchroniczne operacje we/wy. W tym artykule użyto terminów Asynchroniczne i synchroniczne dla operacji we/wy. W tym artykule przyjęto założenie, że czytelnik zna funkcje we/wy plików, takie jak CreateFile, ReadFile, WriteFile.

Często asynchroniczne operacje we/wy zachowują się tak samo jak synchroniczne operacje we/wy. Niektóre warunki omówione w tym artykule w kolejnych sekcjach sprawiają, że operacje we/wy są wykonywane synchronicznie. Obiekt wywołujący nie ma czasu na pracę w tle, ponieważ funkcje we/wy nie zwracają się do momentu ukończenia operacji we/wy.

Kilka funkcji jest powiązanych z synchronicznymi i asynchronicznymi operacjami we/wy. W tym artykule użyto ReadFile i WriteFile jako przykładów. Dobrymi alternatywami byłyby ReadFileEx i WriteFileEx. Chociaż w tym artykule omówiono tylko operacje we/wy dysku, wiele zasad można zastosować do innych typów operacji we/wy, takich jak szeregowe operacje we/wy lub operacje we/wy sieci.

Konfigurowanie asynchronicznych operacji we/wy

Flaga FILE_FLAG_OVERLAPPED musi być określona podczas CreateFile otwarcia pliku. Ta flaga umożliwia asynchroniczne wykonywanie operacji we/wy w pliku. Oto przykład:

HANDLE hFile;

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

if (hFile == INVALID_HANDLE_VALUE)
      ErrorOpeningFile();

Należy zachować ostrożność podczas kodowania asynchronicznego we/wy, ponieważ system zastrzega sobie prawo do synchronicznego wykonania operacji, jeśli jest to konieczne. Dlatego najlepiej jest napisać program, aby poprawnie obsłużyć operację we/wy, która może zostać ukończona synchronicznie lub asynchronicznie. Przykładowy kod przedstawia tę kwestię.

Istnieje wiele rzeczy, które program może zrobić podczas oczekiwania na ukończenie operacji asynchronicznych, takich jak kolejkowanie dodatkowych operacji lub wykonywanie pracy w tle. Na przykład poniższy kod prawidłowo obsługuje nakładające się i nie nakładające się ukończenie operacji odczytu. Nie robi nic więcej niż czekać na wybitne we/wy, aby zakończyć:

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);

Uwaga

&NumberOfBytesRead przekazana do ReadFile elementu różni się od &NumberOfBytesTransferred przekazanej do GetOverlappedResultelementu . Jeśli operacja została wykonana asynchroniczna, GetOverlappedResult jest używana do określenia rzeczywistej liczby bajtów przeniesionych w operacji po jej zakończeniu. Przekazane &NumberOfBytesRead do ReadFile jest bez znaczenia.

Z drugiej strony, jeśli operacja zostanie ukończona natychmiast, przekazana &NumberOfBytesRead do ReadFile jest prawidłowa dla liczby odczytanych bajtów. W takim przypadku zignoruj przekazaną OVERLAPPED strukturę ReadFile; nie używaj jej z GetOverlappedResult ani WaitForSingleObject.

Innym zastrzeżeniem w przypadku operacji asynchronicznej jest to, że nie można używać OVERLAPPED struktury do momentu zakończenia oczekującej operacji. Innymi słowy, jeśli masz trzy wybitne operacje we/wy, musisz użyć trzech OVERLAPPED struktur. Jeśli ponownie użyjesz struktury, otrzymasz nieprzewidywalne OVERLAPPED wyniki operacji we/wy i może wystąpić uszkodzenie danych. Ponadto należy je poprawnie zainicjować, aby żadne dane z pozostałych elementów nie wpływały na nową OVERLAPPED operację, zanim będzie można użyć struktury po raz pierwszy lub przed jej ponownym użyciem po zakończeniu wcześniejszej operacji.

Ten sam typ ograniczenia ma zastosowanie do buforu danych używanego w operacji. Bufor danych nie może być odczytywany ani zapisywany, dopóki nie zostanie ukończona odpowiednia operacja we/wy; odczytywanie lub zapisywanie buforu może powodować błędy i uszkodzone dane.

Asynchroniczne operacje we/wy nadal są synchroniczne

Jeśli jednak wykonasz instrukcje opisane we wcześniejszej części tego artykułu, wszystkie operacje we/wy są nadal zwykle wykonywane synchronicznie w wydanej kolejności i żadna z ReadFile operacji nie zwraca wartości FALSE z zwracanym ERROR_IO_PENDINGelementem GetLastError() , co oznacza, że nie masz czasu na jakąkolwiek pracę w tle. Dlaczego tak się dzieje?

Istnieje wiele powodów, dla których operacje we/wy są wykonywane synchronicznie, nawet jeśli zakodowano operację asynchroniczną.

Kompresji

Jedną z przeszkód dla operacji asynchronicznej jest kompresja systemu plików NTFS (New Technology File System). Sterownik systemu plików nie będzie uzyskiwać dostępu do skompresowanych plików asynchronicznie; Zamiast tego wszystkie operacje są synchroniczne. Ta przeszkoda nie ma zastosowania do plików skompresowanych za pomocą narzędzi podobnych do kompresji lub PKZIP.

Szyfrowanie NTFS

Podobnie jak w przypadku kompresji szyfrowanie plików powoduje, że sterownik systemowy konwertuje asynchroniczne operacje we/wy na synchroniczne. Jeśli pliki zostaną odszyfrowane, żądania we/wy będą asynchroniczne.

Rozszerzanie pliku

Innym powodem, dla którego operacje we/wy są wykonywane synchronicznie, są same operacje. W systemie Windows każda operacja zapisu w pliku, który wydłuża jego długość, będzie synchroniczna.

Uwaga

Aplikacje mogą sprawić, że wspomniana wcześniej operacja zapisu będzie asynchroniczna, zmieniając prawidłową długość danych pliku przy użyciu SetFileValidData funkcji, a następnie wystawiając WriteFilepolecenie .

Za pomocą SetFileValidData (który jest dostępny w systemie Windows XP i nowszych wersjach), aplikacje mogą efektywnie rozszerzać pliki bez ponoszenia kary wydajności za ich zerowe wypełnienie.

Ponieważ system plików NTFS nie wypełnia zerem danych do prawidłowej długości danych (VDL) zdefiniowanej przez SetFileValidDataprogram , ta funkcja ma wpływ na bezpieczeństwo, gdy plik może mieć przypisane klastry, które były wcześniej zajmowane przez inne pliki. W związku z tym wymaga, SetFileValidData aby obiekt wywołujący miał nową SeManageVolumePrivilege włączoną (domyślnie jest ona przypisywana tylko do administratorów). Firma Microsoft zaleca, aby niezależni dostawcy oprogramowania dokładnie rozważyli konsekwencje korzystania z takiej funkcji.

Pamięci podręcznej

Większość sterowników we/wy (dysk, komunikacja i inne) ma specjalny kod przypadku, w którym jeśli żądanie we/wy może zostać zakończone natychmiast, operacja zostanie ukończona, a ReadFile funkcja lub WriteFile zwróci wartość TRUE. Na wszystkie sposoby te typy operacji wydają się być synchroniczne. Zazwyczaj w przypadku urządzenia dyskowego żądanie we/wy można wykonać natychmiast, gdy dane są buforowane w pamięci.

Dane nie są w pamięci podręcznej

Jednak schemat pamięci podręcznej może działać przeciwko Tobie, jeśli dane nie są w pamięci podręcznej. Pamięć podręczna systemu Windows jest implementowana wewnętrznie przy użyciu mapowań plików. Menedżer pamięci w systemie Windows nie zapewnia asynchronicznego mechanizmu błędów strony do zarządzania mapowaniami plików używanymi przez menedżera pamięci podręcznej. Menedżer pamięci podręcznej może sprawdzić, czy żądana strona znajduje się w pamięci, więc w przypadku wystawienia asynchronicznego buforowanego odczytu, a strony nie znajdują się w pamięci, sterownik systemu plików zakłada, że nie chcesz, aby wątek był blokowany, a żądanie będzie obsługiwane przez ograniczoną pulę wątków roboczych. Kontrolka jest zwracana do programu po wywołaniu ReadFile z wciąż oczekującym odczytem.

Działa to dobrze w przypadku niewielkiej liczby żądań, ale ponieważ pula wątków roboczych jest ograniczona (obecnie trzy w systemie 16 MB), nadal będzie tylko kilka żądań umieszczonych w kolejce do sterownika dysku w określonym czasie. Jeśli wystawiasz wiele operacji we/wy dla danych, które nie znajdują się w pamięci podręcznej, menedżer pamięci podręcznej i menedżer pamięci stają się nasycone, a żądania są wykonywane synchronicznie.

Na zachowanie menedżera pamięci podręcznej można również wpływać w zależności od tego, czy uzyskujesz dostęp do pliku sekwencyjnie, czy losowo. Zalety pamięci podręcznej są najbardziej widoczne podczas sekwencyjnego uzyskiwania dostępu do plików. Flaga FILE_FLAG_SEQUENTIAL_SCAN w wywołaniu CreateFile zoptymalizuje pamięć podręczną pod kątem tego typu dostępu. Jeśli jednak uzyskujesz dostęp do plików w sposób losowy, użyj FILE_FLAG_RANDOM_ACCESS flagi in CreateFile , aby poinstruować menedżera pamięci podręcznej, aby zoptymalizować jego zachowanie pod kątem dostępu losowego.

Nie używaj pamięci podręcznej

Flaga FILE_FLAG_NO_BUFFERING ma największy wpływ na zachowanie systemu plików dla operacji asynchronicznej. Jest to najlepszy sposób zagwarantowania, że żądania we/wy są asynchroniczne. Nakazuje systemowi plików, aby w ogóle nie używał żadnego mechanizmu pamięci podręcznej.

Uwaga

Istnieją pewne ograniczenia dotyczące używania tej flagi, które mają związek z wyrównaniem buforu danych i rozmiarem sektora urządzenia. Aby uzyskać więcej informacji, zobacz dokumentację funkcji w dokumentacji funkcji CreateFile dotyczącą prawidłowego używania tej flagi.

Wyniki testów w świecie rzeczywistym

Poniżej przedstawiono niektóre wyniki testów z przykładowego kodu. Wielkość liczb nie jest tu ważna i różni się w zależności od komputera, ale relacja liczb w porównaniu do siebie oświetla ogólny wpływ flag na wydajność.

Możesz spodziewać się wyników podobnych do jednego z następujących:

  • Test 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.
    

    Ten test pokazuje, że wcześniej wspomniany program szybko wystawił 500 żądań we/wy i miał dużo czasu na wykonanie innej pracy lub wykonanie większej liczby żądań.

  • Test 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.
    

    Ten test pokazuje, że ten program spędził 4,495880 sekundy na wywoływaniu pliku ReadFile w celu ukończenia operacji, ale test 1 spędził tylko 0,224264 sekundy, aby wysłać te same żądania. W teście 2 program nie miał dodatkowego czasu na wykonywanie jakichkolwiek prac w tle.

  • Test 3

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

    Ten test pokazuje synchroniczny charakter pamięci podręcznej. Wszystkie odczyty zostały wydane i ukończone w ciągu 0,251670 sekundy. Innymi słowy, żądania asynchroniczne zostały ukończone synchronicznie. Ten test pokazuje również wysoką wydajność menedżera pamięci podręcznej, gdy dane znajdują się w pamięci podręcznej.

  • Test 4

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

    Ten test pokazuje te same wyniki, co w teście 3. Odczyty synchroniczne z pamięci podręcznej są wykonywane nieco szybciej niż odczyty asynchroniczne z pamięci podręcznej. Ten test pokazuje również wysoką wydajność menedżera pamięci podręcznej, gdy dane znajdują się w pamięci podręcznej.

Wniosku

Możesz zdecydować, która metoda jest najlepsza, ponieważ wszystko zależy od typu, rozmiaru i liczby operacji wykonywanych przez program.

Domyślny dostęp do CreateFile plików bez określania żadnych specjalnych flag jest operacją synchroniczną i buforowaną.

Uwaga

W tym trybie otrzymujesz automatyczne zachowanie asynchroniczne, ponieważ sterownik systemu plików wykonuje asynchroniczne odczyty predykcyjne i asynchroniczne leniwe zapisywanie zmodyfikowanych danych. Mimo że to zachowanie nie powoduje asynchronicznego we/wy aplikacji, jest to idealne rozwiązanie dla większości prostych aplikacji.

Z drugiej strony, jeśli aplikacja nie jest prosta, może być konieczne przeprowadzenie profilowania i monitorowania wydajności, aby określić najlepszą metodę, podobną do testów przedstawionych we wcześniejszej części tego artykułu. Przydatne jest profilowanie czasu spędzonego ReadFile w funkcji lub WriteFile , a następnie porównywanie tego czasu z czasem wykonywania rzeczywistych operacji we/wy. Jeśli większość czasu poświęca się na faktyczne wystawianie operacji we/wy, operacje we/wy są wykonywane synchronicznie. Jeśli jednak czas spędzony na wydawaniu żądań we/wy jest stosunkowo mały w porównaniu z czasem potrzebnym na ukończenie operacji we/wy, operacje są traktowane asynchronicznie. Przykładowy kod wymieniony wcześniej w tym artykule używa QueryPerformanceCounter funkcji do wykonywania własnego profilowania wewnętrznego.

Monitorowanie wydajności może pomóc w określeniu, jak wydajnie program korzysta z dysku i pamięci podręcznej. Śledzenie dowolnego licznika wydajności dla obiektu pamięci podręcznej będzie wskazywać wydajność menedżera pamięci podręcznej. Śledzenie liczników wydajności dla obiektów dysków fizycznych lub dysków logicznych będzie wskazywać wydajność systemów dysków.

Istnieje kilka narzędzi, które są przydatne w monitorowaniu wydajności. PerfMon i DiskPerf są szczególnie przydatne. Aby system zbierał dane dotyczące wydajności systemów dyskowych, musisz najpierw wydać DiskPerf polecenie . Po wydaniu polecenia należy ponownie uruchomić system, aby rozpocząć zbieranie danych.

Informacje

Synchroniczne i asynchroniczne operacje we/wy