Membatalkan Operasi I/O Tertunda

Memungkinkan pengguna membatalkan permintaan I/O yang lambat atau diblokir dapat meningkatkan kegunaan dan ketahanan aplikasi Anda. Misalnya, jika panggilan ke fungsi OpenFile diblokir karena panggilan ke perangkat yang sangat lambat, membatalkannya memungkinkan panggilan dilakukan lagi, dengan parameter baru, tanpa mengakhiri aplikasi.

Windows Vista memperluas kemampuan pembatalan dan menyertakan dukungan untuk membatalkan operasi sinkron.

Catatan

Memanggil fungsi CancelIoEx tidak menjamin bahwa operasi I/O akan dibatalkan; driver yang menangani operasi harus mendukung pembatalan dan operasi harus dalam keadaan yang dapat dibatalkan.

Pertimbangan Pembatalan

Saat memprogram panggilan pembatalan, ingatlah pertimbangan berikut:

  • Tidak ada jaminan bahwa driver yang mendasar mendukung pembatalan dengan benar.
  • Saat membatalkan I/O asinkron, ketika tidak ada struktur yang tumpang tindih yang diberikan ke fungsi CancelIoEx , fungsi mencoba untuk membatalkan semua I/O yang luar biasa pada file pada semua utas dalam proses. Setiap utas diproses satu per satu, jadi setelah utas diproses, alur dapat memulai I/O lain pada file sebelum semua utas lain memiliki I/O untuk file yang dibatalkan, menyebabkan masalah sinkronisasi.
  • Saat membatalkan I/O asinkron, jangan gunakan kembali struktur yang tumpang tindih dengan pembatalan yang ditargetkan. Setelah operasi I/O selesai (baik berhasil atau dengan status dibatalkan) maka struktur yang tumpang tindih tidak lagi digunakan oleh sistem dan dapat digunakan kembali.
  • Saat membatalkan I/O sinkron, memanggil fungsi CancelSynchronousIo mencoba membatalkan panggilan sinkron saat ini pada utas. Anda harus berhati-hati untuk memastikan sinkronisasi panggilan sudah benar; panggilan yang salah dalam serangkaian panggilan bisa dibatalkan. Misalnya, jika fungsi CancelSynchronousIo dipanggil untuk operasi sinkron, X, operasi Y hanya dimulai setelah operasi X selesai (biasanya atau dengan kesalahan). Jika utas yang memanggil operasi X kemudian memulai panggilan sinkron lainnya ke X, panggilan pembatalan dapat mengganggu permintaan I/O baru ini.
  • Saat membatalkan I/O sinkron, ketahuilah bahwa kondisi balapan dapat ada setiap kali utas dibagikan di antara berbagai bagian aplikasi, misalnya, dengan utas kumpulan utas.

Operasi yang Tidak Dapat Dibatalkan

Beberapa fungsi tidak dapat dibatalkan menggunakan fungsi CancelIo, CancelIoEx, atau CancelSynchronousIo . Beberapa fungsi ini telah diperluas untuk memungkinkan pembatalan (misalnya, fungsi CopyFileEx ) dan Anda harus menggunakannya sebagai gantinya. Selain mendukung pembatalan, fungsi-fungsi ini juga memiliki panggilan balik bawaan untuk mendukung Anda saat melacak kemajuan operasi. Fungsi berikut tidak mendukung pembatalan:

Untuk informasi selengkapnya, lihat Panduan Penyelesaian/Pembatalan I/O.

Membatalkan I/O Asinkron

Anda dapat membatalkan I/O asinkron dari utas apa pun dalam proses yang mengeluarkan operasi I/O. Anda harus menentukan handel tempat I/O dilakukan dan, secara opsional, struktur tumpang tindih yang digunakan untuk melakukan I/O. Anda dapat menentukan apakah pembatalan terjadi dengan memeriksa status yang dikembalikan dalam struktur yang tumpang tindih atau dalam panggilan balik penyelesaian. Status ERROR_OPERATION_ABORTED menunjukkan bahwa operasi dibatalkan.

Contoh berikut menunjukkan rutinitas yang membutuhkan waktu habis dan mencoba operasi baca, membatalkannya dengan fungsi CancelIoEx jika batas waktu berakhir.

#include <windows.h>

BOOL DoCancelableRead(HANDLE hFile,
                 LPVOID lpBuffer,
                 DWORD nNumberOfBytesToRead,
                 LPDWORD lpNumberOfBytesRead,
                 LPOVERLAPPED lpOverlapped,
                 DWORD dwTimeout,
                 LPBOOL pbCancelCalled)
//
// Parameters:
//
//      hFile - An open handle to a readable file or device.
//
//      lpBuffer - A pointer to the buffer to store the data being read.
//
//      nNumberOfBytesToRead - The number of bytes to read from the file or 
//          device. Must be less than or equal to the actual size of
//          the buffer referenced by lpBuffer.
//
//      lpNumberOfBytesRead - A pointer to a DWORD to receive the number 
//          of bytes read after all I/O is complete or canceled.
//
//      lpOverlapped - A pointer to a preconfigured OVERLAPPED structure that
//          has a valid hEvent.
//          If the caller does not properly initialize this structure, this
//          routine will fail.
//
//      dwTimeout - The desired time-out, in milliseconds, for the I/O read.
//          After this time expires, the I/O is canceled.
// 
//      pbCancelCalled - A pointer to a Boolean to notify the caller if this
//          routine attempted to perform an I/O cancel.
//
// Return Value:
//
//      TRUE on success, FALSE on failure.
//
{
    BOOL result;
    DWORD waitTimer;
    BOOL bIoComplete = FALSE;
    const DWORD PollInterval = 100; // milliseconds

    // Initialize "out" parameters
    *pbCancelCalled = FALSE;
    *lpNumberOfBytesRead = 0;

    // Perform the I/O, in this case a read operation.
    result = ReadFile(hFile,
                  lpBuffer,
                  nNumberOfBytesToRead,
                  lpNumberOfBytesRead,
                  lpOverlapped );

    if (result == FALSE) 
    {
        if (GetLastError() != ERROR_IO_PENDING) 
        {
            // The function call failed. ToDo: Error logging and recovery.
            return FALSE; 
        }
    } 
    else 
    {
        // The I/O completed, done.
        return TRUE;
    }
        
    // The I/O is pending, so wait and see if the call times out.
    // If so, cancel the I/O using the CancelIoEx function.

    for (waitTimer = 0; waitTimer < dwTimeout; waitTimer += PollInterval)
    {
        result = GetOverlappedResult( hFile, lpOverlapped, lpNumberOfBytesRead, FALSE );
        if (result == FALSE)
        {
            if (GetLastError() != ERROR_IO_PENDING)
            {
                // The function call failed. ToDo: Error logging and recovery.
                return FALSE;
            }
            Sleep(PollInterval);
        }
        else
        {
            bIoComplete = TRUE;
            break;
        }
    }

    if (bIoComplete == FALSE) 
    {
        result = CancelIoEx( hFile, lpOverlapped );
        
        *pbCancelCalled = TRUE;

        if (result == TRUE || GetLastError() != ERROR_NOT_FOUND) 
        {
            // Wait for the I/O subsystem to acknowledge our cancellation.
            // Depending on the timing of the calls, the I/O might complete with a
            // cancellation status, or it might complete normally (if the ReadFile was
            // in the process of completing at the time CancelIoEx was called, or if
            // the device does not support cancellation).
            // This call specifies TRUE for the bWait parameter, which will block
            // until the I/O either completes or is canceled, thus resuming execution, 
            // provided the underlying device driver and associated hardware are functioning 
            // properly. If there is a problem with the driver it is better to stop 
            // responding here than to try to continue while masking the problem.

            result = GetOverlappedResult( hFile, lpOverlapped, lpNumberOfBytesRead, TRUE );

            // ToDo: check result and log errors. 
        }
    }

    return result;
}

Membatalkan I/O Sinkron

Anda dapat membatalkan I/O sinkron dari utas apa pun dalam proses yang mengeluarkan operasi I/O. Anda harus menentukan handel ke utas yang saat ini melakukan operasi I/O.

Contoh berikut menunjukkan dua rutinitas:

  • Fungsi SynchronousIoWorker adalah utas pekerja yang mengimplementasikan beberapa I/O file sinkron, dimulai dengan panggilan ke fungsi CreateFile . Jika rutinitas berhasil, rutinitas dapat diikuti oleh operasi tambahan, yang tidak disertakan di sini. Variabel global gCompletionStatus dapat digunakan untuk menentukan apakah semua operasi berhasil atau apakah operasi gagal atau dibatalkan. Variabel global dwOperationInProgress menunjukkan apakah I/O file masih berlangsung.

    Catatan

    Dalam contoh ini, utas UI juga dapat memeriksa keberadaan utas pekerja.

    Pemeriksaan manual tambahan, yang tidak disertakan di sini, diperlukan dalam fungsi SynchronousIoWorker adalah untuk memastikan bahwa jika pembatalan diminta selama periode singkat antara panggilan I/O file, sisa operasi akan dibatalkan.

  • Fungsi MainUIThreadMessageHandler mensimulasikan penangan pesan dalam prosedur jendela utas UI. Pengguna meminta serangkaian operasi file sinkron dengan mengklik kontrol, yang menghasilkan pesan jendela yang ditentukan pengguna, (di bagian yang ditandai oleh WM_MYSYNCOPS). Ini membuat utas baru menggunakan fungsi CreateFileThread , yang kemudian memulai fungsi SynchronousIoWorker adalah. Utas UI terus memproses pesan saat utas pekerja melakukan I/O yang diminta. Jika pengguna memutuskan untuk membatalkan operasi yang belum selesai (biasanya dengan mengklik tombol batalkan), rutinitas (di bagian yang ditandai oleh WM_MYCANCEL) memanggil fungsi CancelSynchronousIo menggunakan handel utas yang dikembalikan oleh fungsi CreateFileThread . Fungsi CancelSynchronousIo kembali segera setelah upaya pembatalan. Terakhir, pengguna atau aplikasi nantinya dapat meminta beberapa operasi lain yang bergantung pada apakah operasi file telah selesai. Dalam hal ini, rutinitas (di bagian yang ditandai oleh WM_PROCESSDATA) pertama-tama memverifikasi bahwa operasi telah selesai dan kemudian menjalankan operasi pembersihan.

    Catatan

    Dalam contoh ini, karena pembatalan dapat terjadi di mana saja dalam urutan operasi, mungkin perlu bagi pemanggil untuk memastikan bahwa status konsisten, atau setidaknya dipahami, sebelum melanjutkan.

// User-defined codes for the message-pump, which is outside the scope 
// of this sample. Windows messaging and message pumps are well-documented
// elsewhere.
#define WM_MYSYNCOPS    1
#define WM_MYCANCEL     2
#define WM_PROCESSDATA  3

VOID SynchronousIoWorker( VOID *pv )
{
    LPCSTR lpFileName = (LPCSTR)pv;
    HANDLE hFile;
    g_dwOperationInProgress = TRUE;    
    g_CompletionStatus = ERROR_SUCCESS;
     
    hFile = CreateFileA(lpFileName,
                        GENERIC_READ,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        0,
                        NULL);


    if (hFile != INVALID_HANDLE_VALUE) 
    {
        BOOL result = TRUE;
        // TODO: CreateFile succeeded. 
        // Insert your code to make more synchronous calls with hFile.
        // The result variable is assumed to act as the error flag here,
        // but can be whatever your code needs.
        
        if (result == FALSE) 
        {
            // An operation failed or was canceled. If it was canceled,
            // GetLastError() returns ERROR_OPERATION_ABORTED.

            g_CompletionStatus = GetLastError();            
        }
             
        CloseHandle(hFile);
    } 
    else 
    {
        // CreateFile failed or was canceled. If it was canceled,
        // GetLastError() returns ERROR_OPERATION_ABORTED.
        g_CompletionStatus = GetLastError();
    }

    g_dwOperationInProgress = FALSE;
}  

LRESULT
CALLBACK
MainUIThreadMessageHandler(HWND hwnd,
                           UINT uMsg,
                           WPARAM wParam,
                           LPARAM lParam)
{
    UNREFERENCED_PARAMETER(hwnd);
    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);
    HANDLE syncThread = INVALID_HANDLE_VALUE;

    //  Insert your initializations here.

    switch (uMsg) 
    {
        // User requested an operation on a file.  Insert your code to 
        // retrieve filename from parameters.
        case WM_MYSYNCOPS:    
            syncThread = CreateThread(
                          NULL,
                          0,
                          (LPTHREAD_START_ROUTINE)SynchronousIoWorker,
                          &g_lpFileName,
                          0,
                          NULL);

            if (syncThread == INVALID_HANDLE_VALUE) 
            {
                // Insert your code to handle the failure.
            }
        break;
    
        // User clicked a cancel button.
        case WM_MYCANCEL:
            if (syncThread != INVALID_HANDLE_VALUE) 
            {
                CancelSynchronousIo(syncThread);
            }
        break;

        // User requested other operations.
        case WM_PROCESSDATA:
            if (!g_dwOperationInProgress) 
            {
                if (g_CompletionStatus == ERROR_OPERATION_ABORTED) 
                {
                    // Insert your cleanup code here.
                } 
                else
                {
                    // Insert code to handle other cases.
                }
            }
        break;
    } 

    return TRUE;
} 

I/O Sinkron dan Asinkron