Sunday, February 7, 2016

Monitoring Files Asynchronously for Reading

There are a number of ways to monitor file descriptors asynchronously to determine when data is available to be read. Calling select() directly is getting to be old fashioned, but it still gets the job done. Because select() can block until data is available to be read, it should only be called from a background thread in a GUI application. In a daemon, it can be called from the primary thread as long as the daemon doesn't need to do anything else while it waits for data to become available.

#import <sys/select.h>
void MonitorFiles( int fileDescriptor1, int fileDescriptor2 )
{
fd_set allFileDescriptorSet;
FD_ZERO( &allFileDescriptorSet );
FD_SET( fileDescriptor1, &allFileDescriptorSet );
FD_SET( fileDescriptor2, &allFileDescriptorSet );
int maxFileDescriptor = MAX( fileDescriptor1, fileDescriptor2 );
for( ;; )
{
fd_set readFileDescriptorSet = allFileDescriptorSet;
int readyCount = select( maxFileDescriptor + 1, &readFileDescriptorSet, NULL, NULL, NULL );
if( readyCount < 0 )
{
// Error
break;
}
if( FD_ISSET( fileDescriptor1, &readFileDescriptorSet ) )
{
// Read data from fileDescriptor1
}
if( FD_ISSET( fileDescriptor2, &readFileDescriptorSet ) )
{
// Read data from fileDescriptor2
}
}
}
view raw select.c hosted with ❤ by GitHub

The kqueue API is similar to select, and has the same limitations with respect to blocking threads.

void MonitorFiles( int fileDescriptor1, int fileDescriptor2 )
{
int kq = kqueue();
if( kq < 0 )
{
// Error
return;
}
struct kevent monitorList[2];
EV_SET( &monitorList[0], fileDescriptor1, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0 );
EV_SET( &monitorList[1], fileDescriptor2, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0 );
struct kevent eventList[2];
for( ;; )
{
int eventCount = kevent( kq, monitorList, 2, eventList, 2, NULL );
if( eventCount < 0 )
{
// Error
break;
}
for( int i = 0; i < eventCount; i++ )
{
// Check eventList[i].flags for errors
// eventList[i].ident is the file descriptor that triggered
// the read event
readDataFromFileDescriptor( eventList[i].ident );
}
}
close( kq );
}
view raw kqueue.c hosted with ❤ by GitHub

Using libdispatch is a better option for GUI applications. The file descriptors can be monitored on a background thread and processed on the main thread.

void MonitorFileDescriptors( int fileDescriptor1, int fileDescriptor2 )
{
dispatch_source_t fd1Source = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, fileDescriptor1, 0UL, dispatch_get_main_queue() );
dispatch_source_set_event_handler( fd1Source, ^{ readDataFromFileDescriptor( fileDescriptor1 ); } );
dispatch_source_set_cancel_handler( fd1Source, ^{
dispatch_release( fd1Source );
close( fileDescriptor1 );
} );
dispatch_resume( fd1Source );
dispatch_source_t fd2Source = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, fileDescriptor2, 0UL, dispatch_get_main_queue() );
dispatch_source_set_event_handler( fd2Source, ^{ readDataFromFileDescriptor( fileDescriptor2 ); } );
dispatch_source_set_cancel_handler( fd2Source, ^{
dispatch_release( fd2Source );
close( fileDescriptor2 );
} );
dispatch_resume( fd2Source );
// Must call either dispatch_main(), [[NSRunLoop currentRunLoop] run], or CFRunLoopRun()
}
view raw gistfile1.txt hosted with ❤ by GitHub

Using NSFileHandle is an even simpler option for GUI applications. The only caveat is that the readability handler is called on a random thread. If you need to update your UI in response to something read from the file descriptor, you will need to move over to the main thread.

void MonitorFiles( int fileDescriptor1, int fileDescriptor2 )
{
NSFileHandle * fh1 = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor1 closeOnDealloc:NO];
fh1.readabilityHandler = ^( NSFileHandle *fh ) {
// readabilityHandler is called on a random thread. Move it back to the main thread
dispatch_sync( dispatch_get_main_thread(), ^{ readDataFromFileDescriptor( fh.fileDescriptor ) } );
}]
NSFileHandle * fh2 = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor2 closeOnDealloc:NO];
fh2.readabilityHandler = ^( NSFileHandle *fh ) {
// readabilityHandler is called on a random thread. Move it back to the main thread
dispatch_sync( dispatch_get_main_thread(), ^{ readDataFromFileDescriptor( fh.fileDescriptor ) } );
}]
// Must call either [[NSRunLoop currentRunLoop] run] or CFRunLoopRun()
}
view raw NSFileHandle.m hosted with ❤ by GitHub

No comments:

Post a Comment