Implementing Non-blocking Synchronous I/O with Polling
Overview
This implementation demonstrates a non-blocking synchronous I/O approach using polling mechanisms. The underlying I/O operations are inherently blocking, so to avoid hanging the main thread, these operations are offloaded into separate threads.
Implementation Details
The core concept involves encapsulating the I/O execution within a thread clas called CBaseThread. The actual I/O work is performed inside a dedicated thread function:
unsigned CSyncIOByPolling::ThreadWork()
{
return IO();
}
In the OnStart() method, two worker threads are initiated to handle I/O tasks concurrently. A polling loop then checks the status of each thread until all operations complete.
bool CSyncIOByPolling::OnStart()
{
int nRetArray[] = {-1, -1};
HANDLE hThreadArray[] = {NULL, NULL};
int nThreadNum = sizeof(hThreadArray) / sizeof(hThreadArray[0]);
for (int i = 0; i < nThreadNum; i++)
{
hThreadArray[i] = StartThread();
}
int nIndex = 0;
int nCompletedNum = 0;
// Poll for completion of I/O operations
while (true)
{
for (int i = 0; i < nThreadNum; i++)
{
if (hThreadArray[i])
{
NotifyProgress(nIndex, i);
DWORD dwExitCode = STILL_ACTIVE;
if (::GetExitCodeThread(hThreadArray[i], &dwExitCode))
{
if (STILL_ACTIVE != dwExitCode)
{
nRetArray[i] = dwExitCode;
}
else
{
continue;
}
}
::CloseHandle(hThreadArray[i]);
hThreadArray[i] = NULL;
NotifyResult(nRetArray[i], i);
nCompletedNum++;
}
}
if (nCompletedNum >= nThreadNum)
{
break;
}
Sleep(TIMER_ELAPSE);
nIndex += TIMER_ELAPSE;
}
return true;
}
Compared to purely synchronous blocking I/O, this approach introduces additional complexity due to the need for thread management and polling logic.
Testing Observations
Similar to synchronous blocking behavior, the UI remains unresponsive during execution, with the "Stop" button disabled. However, there are key differences:
-
Progress updates are visible even before I/O results are available. This provides visual feedback to users that the application is still active rather than frozen.
-
In cases where the I/O operation does not support timeouts, a maximum polling duration can be enforced to prevent indefinite blocking of the main thread.
-
Concurrent execution through threading allows multiple I/O tasks to run simultaneously, potentially reducing total processing time.
From a usability standpoint, this pattern offers better responsiveness than pure synchronous blocking methods.
However, performance considerations exist regarding polling interval settings. Too frequent polling consumes unnecessary CPU cycles, while overly long intervals may delay perceived completion times. For instance, if an operation finishes in 10 milliseconds but the polling interval is set to 1 second, the effective wait time increases unnecessarily.