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 GetOverlappedResult
elementu . 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_PENDING
elementem 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 WriteFile
polecenie .
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 SetFileValidData
program , 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.