The Windows File Management 16

 

 

 

 

 

Canceling Synchronous I/O

 

You can cancel synchronous I/O from any thread in the process that issued the I/O operation. You must specify the handle to the thread which is currently performing the I/O operation. The following example shows two routines:

  1. The SynchronousIoWorker() routine is a worker thread that implements some synchronous file I/O, starting with a call to the CreateFile() function. If the routine is successful, the routine can be followed by additional operations, which are not included here. The global variable gCompletionStatus can be used to determine whether all operations succeeded or whether an operation failed or was canceled. The global variable dwOperationInProgress indicates whether file I/O is still in progress. Take note that in this example, the UI thread could also check for the existence of the worker thread. Additional manual checks, which are not included here, are required in the SynchronousIoWorker() routine to ensure that if the cancellation was requested during the brief periods between file I/O calls, the rest of the operations will be canceled.
  2. The MainUIThreadMessageHandler() routine simulates the message handler within a UI thread's window procedure. The user requests a set of synchronous file operations by clicking a control, which generates a user-defined window message, (in the section marked by WM_MYSYNCOPS). This creates a new thread using the CreateFileThread() routine, which then starts the SynchronousIoWorker() routine. The UI thread continues to process messages while the worker thread performs the requested I/O. If the user decides to cancel the unfinished operations (typically by clicking a cancel button), the routine (in the section marked by WM_MYCANCEL) calls the CancelSynchronousIo() function using the thread handle returned by the CreateFileThread() routine. The CancelSynchronousIo() function returns immediately after the cancellation attempt. Finally, the user or application may later request some other operation that depends on whether the file operations have completed. In this case, the routine (in the section marked by WM_PROCESSDATA) first verifies that the operations have completed and then executes the clean-up operations. Note that in the following code snippet example, since the cancellation could have occurred anywhere in the sequence of operations, it may be necessary for the caller to ensure that the state is consistent, or at least understood, before proceeding.

 

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

{

      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;

}

 

Alertable I/O

 

Alertable I/O is the method by which application threads process asynchronous I/O requests only when they are in an alertable state. To understand when a thread is in an alertable state, consider the following scenario:

  1. A thread initiates an asynchronous read request by calling ReadFileEx() with a pointer to a callback function.
  2. The thread initiates an asynchronous write request by calling WriteFileEx() with a pointer to a callback function.
  3. The thread calls a function that fetches a row of data from a remote database server.

 

In this scenario, the calls to ReadFileEx() and WriteFileEx() will most likely return before the function call in step 3. When they do, the kernel places the pointers to the callback functions on the thread's Asynchronous Procedure Call (APC) queue. The kernel maintains this queue specifically to hold returned I/O request data until it can be processed by the corresponding thread. When the row fetch is complete and the thread returns from the function, its highest priority is to process the returned I/O requests on the queue by calling the callback functions. To do this, it must enter an alertable state. A thread can only do this by calling one of the following functions with the appropriate flags:

  1. SleepEx()
  2. WaitForSingleObjectEx()
  3. WaitForMultipleObjectsEx()
  4. SignalObjectAndWait()
  5. MsgWaitForMultipleObjectsEx()

 

When the thread enters an alertable state, the following events occur:

 

  1. The kernel checks the thread's APC queue. If the queue contains callback function pointers, the kernel removes the pointer from the queue and sends it to the thread.
  2. The thread executes the callback function.
  3. Steps 1 and 2 are repeated for each pointer remaining in the queue.
  4. When the queue is empty, the thread returns from the function that placed it in an alertable state.

 

In this scenario, once the thread enters an alertable state it will call the callback functions sent to ReadFileEx() and WriteFileEx(), then return from the function that placed it in an alertable state. If a thread enters an alertable state while its APC queue is empty, the thread's execution will be suspended by the kernel until one of the following occurs:

 

  1. The kernel object that is being waited on becomes signaled.
  2. A callback function pointer is placed in the APC queue.

 

A thread that uses alertable I/O processes asynchronous I/O requests more efficiently than when they simply wait on the event flag in the OVERLAPPED structure to be set, and the alertable I/O mechanism is less complicated than I/O completion ports to use. However, alertable I/O returns the result of the I/O request only to the thread that initiated it. I/O completion ports do not have this limitation.

 

 

 

 

< Windows Files 15 | Win32 Programming | Win32 File Index | Windows Files 17 >