Asynchronous programming in C++ using resumable functions and await

Asynchronous programming in C++ using resumable functions and await

Rate This
  • Comments 9

As you know we recently released the Visual C++ Compiler November 2013 CTP. One of the many features in this CTP is the support for resumable functions and await. In this blog post, I want to touch upon some examples where these features make the experience of programming with asynchronous API much simpler.

Example 1

The first example we are going to look at is the official File picker sample for Windows 8.1. If you open the solution for this sample using Visual Studio 2013, build and run, you will see an app like the below. Selecting Option 4 in this sample brings up the file picker dialog which allows you to save a simple text file.

clip_image001

In the project file Scenario4.xaml.cpp, the member function “Scenario4::SaveFileButton_Click” contains the implementation of bringing up file picker and writing to the saved file location. I have removed some code comments for brevity.

 

Code without await:  

void Scenario4::SaveFileButton_Click(Object^ sender, RoutedEventArgs^ e)
{
    rootPage->ResetScenarioOutput(OutputTextBlock);
 
    FileSavePicker^ savePicker = ref new FileSavePicker();
    savePicker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary;
 
    auto plainTextExtensions = ref new Platform::Collections::Vector<String^>();
    plainTextExtensions->Append(".txt");
    savePicker->FileTypeChoices->Insert("Plain Text", plainTextExtensions);
    savePicker->SuggestedFileName = "New Document";
 
    create_task(savePicker->PickSaveFileAsync()).then([this](StorageFile^ file)
    {
        if (file != nullptr)
        {
            CachedFileManager::DeferUpdates(file);
            create_task(FileIO::WriteTextAsync(file, file->Name)).then([this, file]()
            {

                create_task(CachedFileManager::CompleteUpdatesAsync(file)).then([this, file]

                    (FileUpdateStatus status)

                {
                    if (status == FileUpdateStatus::Complete)
                        OutputTextBlock->Text = "File " + file->Name + " was saved.";
                    else
                        OutputTextBlock->Text = "File " + file->Name + " couldn't be saved.";
                });
            });
        }
        else
        {
            OutputTextBlock->Text = "Operation cancelled.";
        }
    });
}

 

Above code uses PPL Tasksto call Windows Runtime asynchronous API by providing lambdas to handle the results of those API.

Let’s make a few changes to this code now:

  • I’ll assume that you have already downloaded and installed the November CTP.
  • In the project properties, change the platform toolset to “Visual C++ Compiler Nov 2013 CTP (CTP_Nov2013)
  • Open up the file Scenario4.xaml.h and to the class “Scenario4”, add a private function with the following signature:

void SaveFileButtonWithAwait() __resumable;

  • Open up the file Scenario4.xaml.cpp and below the existing include statements, add the following:

#include <pplawait.h>

  • In the same file, go to the existing member function “Scenario4::SaveFileButton_Click” and comment out all of its contents. Instead add a simple call to the newly added member function:

SaveFileButtonWithAwait();

  • Provide the implementation of the member function we earlier added to the header file. The code looks like:

 

Code with await:

void Scenario4::SaveFileButtonWithAwait() __resumable
{
    rootPage->ResetScenarioOutput(OutputTextBlock);
 
    FileSavePicker^ savePicker = ref new FileSavePicker();
    savePicker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary;
 
    auto plainTextExtensions = ref new Platform::Collections::Vector<String^>();
    plainTextExtensions->Append(".txt");
    savePicker->FileTypeChoices->Insert("Plain Text", plainTextExtensions);
    savePicker->SuggestedFileName = "New Document";
 
    auto file = __await savePicker->PickSaveFileAsync();
    if (file != nullptr)
    {
        CachedFileManager::DeferUpdates(file);
        __await FileIO::WriteTextAsync(file, file->Name);
        auto status = __await CachedFileManager::CompleteUpdatesAsync(file);
        if (status == FileUpdateStatus::Complete)
        {
            OutputTextBlock->Text = "File " + file->Name + " was saved.";
        }
        else
        {
            OutputTextBlock->Text = "File " + file->Name + " couldn't be saved.";
        }
    }
    else
    {
        OutputTextBlock->Text = "Operation cancelled.";
    }
}


Above code uses await to - well - wait for the result of the asynchronous API. If you contrast this code (using await) with the earlier code (using PPL tasks), you will agree that while both get the job done, the latter is definitely better looking.

Example 2

Another example (not present as an online sample but used in a real app) is the below code. It basically calls the Windows Runtime FilePicker API to select multiple pictures and then creates multiple tasks to copy all of the selected files to the application’s temporary folder. Before proceeding it needs to wait till all files are copied.

 

Code without await:

void XamlSpiro::MainPage::loadImagesWithPPL()
{
    auto openPicker = ref new FileOpenPicker();
    openPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary;
    openPicker->ViewMode = PickerViewMode::Thumbnail;
    openPicker->FileTypeFilter->Append("*");
 

    task<IVectorView<StorageFile^>^>(openPicker->PickMultipleFilesAsync()).then([this]

        (IVectorView<StorageFile^>^ fileVector)

    {
        for (auto file : fileVector)
        {

            m_copyTasks.push_back(create_task(file->CopyAsync(

                ApplicationData::Current->TemporaryFolder,

                file->Name, NameCollisionOption::ReplaceExisting)));

        }
 
        when_all(begin(m_copyTasks), end(m_copyTasks)).then([this](std::vector<StorageFile^> results)
        {
            for (auto copiedFile : results)
            {
                InputFilesVector->Append(copiedFile);
            }
        }).then([this]()
        {
            DisplayImages();
        });
    });
}

 

Code with await:

void XamlSpiro::MainPage::loadImagesWithAwait() __resumable
{
    auto openPicker = ref new FileOpenPicker();
    openPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary;
    openPicker->ViewMode = PickerViewMode::Thumbnail;
    openPicker->FileTypeFilter->Append("*");
 
    auto fileVector = __await openPicker->PickMultipleFilesAsync();
 
    for (auto file : fileVector)
    {

        m_copyTasks.push_back(create_task(file->CopyAsync(ApplicationData::Current->TemporaryFolder,

            file->Name, NameCollisionOption::ReplaceExisting)));

    }
 
    auto results = __await when_all(begin(m_copyTasks), end(m_copyTasks));
    for (auto copiedFile : results)
    {
        InputFilesVector->Append(copiedFile);
    }
    DisplayImages();
}

 

A subtle difference in this case is that we are not unnecessarily calling await for every CopyAsync call. That would have been sub-optimal. Instead we are still creating individual tasks for all copy operations and calling await only on the when_all operation so that we wait only for the required amount of time, no more, no less.

As you might know, Windows Store world is full of asynchronous Windows Runtime API. So these features are especially helpful for Store app development. They provide a synchronous way of thinking about code which needs to compose asynchronous calls. We hope you will try these features out and let us know your feedback.

  • So these features are especially helpful for Store app development.

    ?Does any of this work in normal Windows?

  • Could you explain the implementation details?

    Is it similar to how it was done in C#/VB?

  • @Eman Yes of course.  These features work for any kind of asynchronous API whether in Store apps or Desktop apps.  I mentioned Store apps specifically because new Windows API available for Store apps are mostly async API.

    @OmariO You can find more details about implementation details in this Channel9 video: channel9.msdn.com/.../Bringing-await-to-Cpp

  • This async/await feature is really great. I hope someday this get into official ISO/IEC standard.

  • But this only works for that CTP.  No other compiler in the world understands it, and no other compiler other than perhaps a future MS compiler ever will.  Is that the way it is?

  • @N. Amazement

    No, resumable functions and await will most likely be in ISO C++ in the near future. It has been proposed for C++17.  

  • The problem I have with async/await is it's almost like the Rust programming language's do statement and that with a little more tweaking it could be made a more useful syntax sugar than merely supporting async code

    e.g. you can make the code:

    T sum{};

    auto a = do each_range(a,b) {

    in auto e;

    if (e < decltype(e){}) {

    break;

    }

    result sum += e;

    }

    cout << sum + a;

    be syntax sugar for:

    T sum{};

    sequence(

    each_range(a, b, [&sum](auto e){

    if (e < decltype(e){}) {

    break();

    }

    return sum += e;

    }),

    [&sum](auto a){ cout << sum + a; }

    );

  • Hi, these features seem very handy but I have a couple of questions:

    In the examples above - what's the difference between __await and std::async? I know that std::async has a problem with blocking future destructor but in this case we wait anyway so that's not an issue here.

    Is the __resumable keyword really necessary? The real asynchronous bits are marked with __await so why do we need to also mark the calling method? Compiler knows the point of origin anyway, doesn't it?

  • @Krzysztof Kawa:

    > what's the difference between __await and std::async?

    std::async creates a future than can (depending on the launching policy) run concurrently with the caller.

    The __await take a future (a PPL task) and asynchronously extracts the value out of it. Think of it as a non-blocking 'get'.

    > Is the __resumable keyword really necessary?

    It is there for several reasons.

    1) You can return a value from a __resumable function, and the compiler will turn it into a task:

    task<int> g() __resumable

    {

       ...

       return 42;

    }

    2) When 'await' becomes a proper keyword, it can be safely introduced into the language because it can

    only used in resumable functions, so it will not break user code.

    3) Resumable function have certain restrictions: it must return a task (or void in C++ CX),

    cannot have varargs, etc.

    4) 'resumable' is a useful visual clue to the programmer, indicating that the function is capable of

    split-phase execution: it can be suspended and return before producing a final result, and then resume execution,

    potentially multiple times.

Page 1 of 1 (9 items)