Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Hello World, asynchronously!

We might as well jump straight in: on the left is a traditional STL iostreams implementation with its equivalent in AFIO free functions on the right.

Table 1.10. Traditional STL iostreams and AFIO side by side

try
{
  // Open a file called example_file.txt
  std::fstream openfile("example_file.txt"); 1
  // Turn on exception throwing
  openfile.exceptions(std::fstream::failbit | std::fstream::badbit);

  // Do a write gather. STL iostreams will buffer the writes
  // and coalesce them into a single syscall
  openfile << "He"; 2
  openfile << "ll";
  openfile << "o ";
  openfile << "Wo";
  openfile << "rl";
  openfile << "d\n";

  // Make sure the previous batch has definitely reached physical storage
  // This won't complete until the write is on disc
  openfile.sync(); 3

  // Fill this array from the file
  std::array<char, 12> buffer;
  openfile.seekg(0, std::ios::beg);
  openfile.read(buffer.data(), buffer.size()); 4

  // Close the file and delete it
  openfile.close();                                    5
  boost::afio::filesystem::remove("example_file.txt"); 6

  // Convert the read array into a string
  std::string contents(buffer.begin(), buffer.end());
  std::cout << "Contents of file is '" << contents << "'" << std::endl;
}
catch(...)
{
  std::cerr << boost::current_exception_diagnostic_information(true) << std::endl;
  throw;
}

1

opens file

2

writes

3

syncs

4

reads

5

closes file

6

deletes file

namespace afio = BOOST_AFIO_V2_NAMESPACE;
namespace asio = BOOST_AFIO_V2_NAMESPACE::asio;

// Set a dispatcher as current for this thread
afio::current_dispatcher_guard h(afio::make_dispatcher().get());

// Schedule an opening of a file called example_file.txt
afio::future<> openfile = afio::async_file(
"example_file.txt", afio::file_flags::create | afio::file_flags::read_write);

// Something a bit surprising for many people is that writing off
// the end of a file in AFIO does NOT extend the file and writes
// which go past the end will simply fail instead. Why not?
// Simple: that's the convention with async file i/o, because
// synchronising multiple processes concurrently adjusting a
// file's length has significant overhead which is wasted if you
// don't need that functionality. Luckily, there is an easy
// workaround: either open a file for append-only access, in which
// case all writes extend the file for you, or else you explicitly
// extend files before writing, like this:
afio::future<> resizedfile = afio::async_truncate(openfile, 12);

// Config a write gather. You could do this of course as a batch
// of writes, but a write gather has optimised host OS support in most
// cases, so it's one syscall instead of many.
std::vector<asio::const_buffer> buffers;
buffers.push_back(asio::const_buffer("He", 2));
buffers.push_back(asio::const_buffer("ll", 2));
buffers.push_back(asio::const_buffer("o ", 2));
buffers.push_back(asio::const_buffer("Wo", 2));
buffers.push_back(asio::const_buffer("rl", 2));
buffers.push_back(asio::const_buffer("d\n", 2));
// Schedule the write gather to offset zero after the resize file
afio::future<> written(afio::async_write(resizedfile, buffers, 0));

// Have the compiler config the exact same write gather as earlier for you
// The compiler assembles an identical sequence of ASIO write gather
// buffers for you
std::vector<std::string> buffers2 = {"He", "ll", "o ", "Wo", "rl", "d\n"};
// Schedule this to occur after the previous write completes
afio::future<> written2(afio::async_write(written, buffers2, 0));

// Schedule making sure the previous batch has definitely reached physical
// storage
// This won't complete until the write is on disc
afio::future<> stored(afio::async_sync(written2));

// Schedule filling this array from the file. Note how convenient std::array
// is and completely replaces C style char buffer[bytes]
std::array<char, 12> buffer;
afio::future<> read(afio::async_read(stored, buffer, 0));

// Schedule the closing and deleting of example_file.txt after the contents read
afio::future<> deletedfile(afio::async_rmfile(afio::async_close(read)));

// Wait until the buffer has been filled, checking all steps for errors
afio::when_all_p(openfile, resizedfile, written, written2, stored, read)
.get(); 1

// There is actually a io_req<std::string> specialisation you
// can use to skip this bit by reading directly into a string ...
std::string contents(buffer.begin(), buffer.end());
std::cout << "Contents of file is '" << contents << "'" << std::endl;

// Check remaining ops for errors
deletedfile.get();

1

waits for file open, resize, write, sync and read to complete, throwing any exceptions encountered


AFIO is based on future continuations as will be in the standard C++ library from C++ 1z onwards. Each continuation enforces an order of execution between the asynchronous operations.

What this means is straightforward: in the example above async_file() outputs on completion an open file handle which is fed to async_truncate() which on completion feeds the same input handle to async_write() and so on. async_close() takes in the open file handle, closes it, and outputs a null file handle on completion to async_rmfile() which can still retrieve its last known path before being closed.


PrevUpHomeNext