Udostępnij za pośrednictwem


Asynchroniczne operacje we/wy dysku są wyświetlane jako synchroniczne w systemie Windows

Ten artykuł pomaga rozwiązać problem polegający na tym, że domyślne zachowanie we/wy jest synchroniczne, ale jest ono 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 działanie dla operacji we/wy jest synchroniczne, co oznacza, że funkcja we/wy jest wywoływana i zwraca wynik po zakończeniu operacji we/wy. Asynchroniczne operacje we/wy pozwalają na natychmiastowe zwrócenie kontroli wywołującemu, jednak operacje te nie są uznawane za ukończone aż do późniejszego momentu. System operacyjny powiadamia obiekt wywołujący, gdy operacja we/wy zostanie zakończona. Zamiast tego osoba wywołująca może określić stan zalegającej operacji we/wy, korzystając z usług systemu operacyjnego.

Zaletą asynchronicznego we/wy jest to, że wywołujący ma czas na wykonanie innej pracy lub składanie większej liczby żądań podczas wykonywania operacji we/wy. Termin Overlapped I/O jest często używany w przypadku operacji wejścia/wyjścia asynchronicznych, a Non-overlapped I/O dla operacji wejścia/wyjścia synchronicznych. W tym artykule użyto terminów asynchroniczne i synchroniczne dla operacji we/wy. W tym artykule założono, że czytelnik ma znajomość funkcji we/wy plików, takich jak CreateFile, , ReadFileWriteFile.

Często asynchroniczne operacje we/wy działają tak samo jak synchroniczne. Niektóre warunki, które są omawiane 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 elementów ReadFile i WriteFile jako przykładów. Dobrymi alternatywami byłyby ReadFileEx i WriteFileEx. Mimo że w tym artykule omówiono tylko we/wy dysku, wiele zasad można zastosować do innych typów we/wy, takich jak szeregowe we/wy lub sieciowe we/wy.

Skonfiguruj asynchroniczne we/wy

Flaga FILE_FLAG_OVERLAPPED musi być określona w CreateFile podczas otwierania 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ługiwać operację we/wy, którą można wykonać synchronicznie lub asynchronicznie. Przykładowy kod demonstruje tę kwestie.

Istnieje wiele rzeczy, które program może wykonać podczas oczekiwania na zakoń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 niezakładające się zakończenie operacji odczytu. Nie robi nic innego niż czeka na ukończenie oczekujących operacji we/wy.

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 przekazane do ReadFile różni się od &NumberOfBytesTransferred przekazanego do GetOverlappedResult. Jeśli operacja została wykonana asynchronicznie, GetOverlappedResult zostanie użyta do określenia rzeczywistej liczby bajtów przeniesionych w operacji po jej zakończeniu. Przekazany &NumberOfBytesRead do ReadFile jest bezsensowny.

Z drugiej strony, jeśli operacja zostanie ukończona natychmiast, wówczas &NumberOfBytesRead przekazany do ReadFile jest prawidłowy względem liczby odczytanych bajtów. W takim przypadku zignoruj strukturę OVERLAPPED przekazaną do elementu ReadFile; nie używaj jej z elementem GetOverlappedResult lub WaitForSingleObject.

Kolejnym zastrzeżeniem operacji asynchronicznej jest to, że nie wolno używać struktury OVERLAPPED, dopóki nie zakończy się jej trwająca operacja. Innymi słowy, jeśli masz trzy niezakończone operacje we/wy, musisz użyć trzech struktur OVERLAPPED. Jeśli ponownie użyjesz OVERLAPPED struktury, otrzymasz nieprzewidywalne wyniki operacji we/wy i może wystąpić uszkodzenie danych. Ponadto należy je poprawnie zainicjować, aby żadne dane pozostawione nie wpływały na nową operację, zanim będzie można użyć struktury po raz pierwszy lub przed ponownym użyciem OVERLAPPED jej po zakończeniu wcześniejszej operacji.

Ten sam typ ograniczenia dotyczy buforu danych używanego w operacji. Bufor danych nie może być odczytywany ani zapisywany do momentu zakończenia odpowiedniej operacji we/wy; odczytywanie lub zapisywanie buforu może powodować błędy i uszkodzone dane.

Asynchroniczne we/wy nadal wydają się być synchroniczne

Jeśli jednak postępowałeś według instrukcji opisanych wcześniej w tym artykule, wszystkie operacje we/wy są nadal zazwyczaj wykonywane synchronicznie w porządku wydawania, a żadna z ReadFile operacji nie zwraca wartości FALSE wraz ze zwracaniem GetLastError()ERROR_IO_PENDING, co oznacza, że nie jest możliwe wykonanie żadnej pracy w tle. Dlaczego tak się dzieje?

Istnieje wiele powodów, dla których operacje we/wy kończą się synchronicznie, nawet jeśli były zaplanowane jako asynchroniczne.

Kompresja

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

Szyfrowanie NTFS

Podobnie jak kompresja, 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, jest natura samych operacji. W systemie Windows każda operacja zapisu w pliku, który wydłuża jego długość, będzie synchroniczna.

Uwaga

Aplikacje mogą asynchroniczne wykonać wcześniej wymienioną operację zapisu, zmieniając prawidłową długość danych pliku przy użyciu SetFileValidData funkcji , a następnie wydając element WriteFile.

Za pomocą SetFileValidData (dostępnej w systemie Windows XP i nowszych wersjach), aplikacje mogą wydajnie rozszerzać pliki bez obniżenia wydajności związanego z wypełnianiem ich zerami.

Ponieważ system plików NTFS nie wypełnia zerami danych do prawidłowej długości danych (VDL), która jest zdefiniowana przez SetFileValidData, ta funkcja ma implikacje dla bezpieczeństwa, gdyż plik może być przypisany do klastrów wcześniej zajmowanych przez inne pliki. W związku z tym SetFileValidData wymaga, aby wywołujący miał włączone nowe SeManageVolumePrivilege (domyślnie jest to przypisane tylko administratorom). Firma Microsoft zaleca, aby niezależni dostawcy oprogramowania starannie rozważyli implikacje korzystania z takiej funkcji.

Pamięć podręczna

Większość sterowników we/wy (dysków, komunikacyjnych i innych) posiada specjalne przypadki kodu, w których, jeśli żądanie we/wy może zostać natychmiast ukończone, operacja zostanie zakończona, a funkcja ReadFile lub WriteFile zwróci wartość TRUE. Na wszystkie sposoby te typy operacji wydają się być synchroniczne. W przypadku urządzenia dyskowego zazwyczaj żą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 stronicowania 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 jest w pamięci, więc jeśli wykonasz asynchroniczny, buforowany odczyt, a jeśli strony nie znajdują się w pamięci, sterownik systemu plików zakłada, że nie chcesz, aby wątek został zablokowany, a żądanie ma być obsłużone przez ograniczoną pulę wątków roboczych. Sterowanie jest przekazywane do twojego programu po wywołaniu ReadFile, gdy czytanie nadal w toku.

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ń w kolejce do sterownika dysku w określonym czasie. Jeśli zainicjujesz 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 zostaną przeciążone, a żądania będą wykonywane synchronicznie.

Zachowanie menedżera pamięci podręcznej może mieć również wpływ na to, czy uzyskujesz dostęp do pliku sekwencyjnie, czy losowo. Zalety pamięci podręcznej są widoczne najczęściej podczas uzyskiwania dostępu do plików sekwencyjnie. Flaga FILE_FLAG_SEQUENTIAL_SCAN w wywołaniu CreateFile zoptymalizuje pamięć podręczną dla tego typu dostępu. Jeśli jednak uzyskujesz dostęp do plików w sposób losowy, użyj flagi FILE_FLAG_RANDOM_ACCESS w programie CreateFile, aby menedżer pamięci podręcznej zoptymalizował 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 wejścia/wyjścia 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 muszą mieć związek z wyrównaniem buforu danych i rozmiarem sektora urządzenia. Aby uzyskać więcej informacji, zobacz dokumentację funkcji CreateFile dotyczącą prawidłowego używania tej flagi.

Wyniki testów w świecie rzeczywistym

Poniżej przedstawiono kilka wyników testu z przykładowego kodu. Wielkość liczb nie jest tutaj ważna i różni się od komputera do komputera, ale relacja liczb w porównaniu ze sobą oświetla ogólny wpływ flag na wydajność.

Możesz oczekiwać, że wyniki będą podobne 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 wymieniony program szybko wystawił 500 żądań we/wy i miał dużo czasu na wykonywanie innych czynności lub wystawianie 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 sekund wywołując plik ReadFile w celu ukończenia operacji, ale test 1 spędził tylko 0,224264 sekundy, aby wysyłać te same żądania. W teście 2 nie było dodatkowego czasu, aby program działał 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 demonstruje synchroniczną naturę pamięci podręcznej. Wszystkie odczyty zostały wykonane i ukończone w 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 są 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. Synchroniczne operacje odczytu z pamięci podręcznej są nieco szybsze niż operacje asynchroniczne odczytu z pamięci podręcznej. Ten test pokazuje również wysoką wydajność menedżera pamięci podręcznej, gdy dane są w pamięci podręcznej.

Podsumowanie

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 pliku bez określania żadnych flag specjalnych do CreateFile to operacja synchroniczna i buforowana.

Uwaga

W tym trybie występuje pewne automatyczne zachowanie asynchroniczne, ponieważ sterownik systemu plików wykonuje predykcyjne asynchroniczne operacje odczytu z wyprzedzeniem i asynchroniczne leniwe zapisywanie zmodyfikowanych danych. Chociaż to zachowanie nie czyni operacji we/wy w aplikacji asynchronicznymi, jest to idealny przypadek dla zdecydowanej większości prostych aplikacji.

Z drugiej strony, jeśli aplikacja nie jest prosta, może być konieczne przeprowadzenie profilowania i monitorowania wydajności w celu określenia najlepszej metody, podobnie jak w testach przedstawionych wcześniej w tym artykule. Profilowanie czasu spędzonego w funkcji ReadFile lub WriteFile, a następnie porównywanie tego czasu z czasem potrzebnym do ukończenia rzeczywistych operacji wejścia/wyjścia jest przydatne. Jeśli większość czasu jest poświęcana na faktyczne wydawanie operacji we/wy, to operacje we/wy wykonywane są synchronicznie. Jednak jeśli czas spędzony na wystawianiu żądań we/wy jest stosunkowo mały w porównaniu z czasem wymaganym do ukończenia operacji we/wy, to Twoje operacje są traktowane asynchronicznie. Przykładowy kod wymieniony wcześniej w tym artykule używa funkcji QueryPerformanceCounter do wykonywania własnego wewnętrznego profilowania.

Monitorowanie wydajności może pomóc w ustaleniu, jak efektywnie 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 fizyczny lub Dysk logiczny 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 dysków, należy najpierw wydać DiskPerf polecenie . Po wydaniu polecenia należy ponownie uruchomić system, aby uruchomić zbieranie danych.

Bibliografia

Synchroniczne i asynchroniczne operacje wejścia/wyjścia