共用方式為


Try It Now: Use PPL to Produce Windows 8 Asynchronous Operations

If you’ve seen any of the Windows Runtime talks from //build/ (like this one), one of the things you’ll discover is that asynchronous APIs are pervasive throughout it. Operations which can potentially be long running – particularly things like I/O – are often exposed solely through an asynchronous pattern.

Because of this, it is, in fact, quite common for an application to require the use of asynchronous APIs in order to accomplish work. The usage of those APIs in their raw form can be somewhat complex. In a previous blog post, we discussed how the PPL’s tasks model can make working with such asynchronous APIs much easier for C++ programmers.

While the tasks model makes it easy to utilize asynchronous operations that are built elsewhere, how can a C++ programmer using the PPL construct their own asynchronous operations and APIs and how can those interoperate with the Windows Runtime and other supported languages? The answer lies again in the tasks model.

While we aren’t making tasks themselves Windows Runtime objects, we are introducing a simple and unified way to consider a task a Windows Runtime asynchronous action or operation. This is all through a single new API in the Concurrency namespace: create_async.


create_async

create_async is a template API which provides a way to turn a task into a Windows Runtime asynchronous action or operation representing the completion of that task. In its simplest form, create_async takes a function object which would otherwise be a task body, internally creates a task, wraps that task in an IAsync* interface, and returns it:

 IAsyncOperation<int>^ asyncOp = create_async( []() -> int {
     return 42;
 }); 

 

The function object passed to the simple create_async example above behaves exactly the same as an identical one used in a task<int> constructor as shown below. It is in fact a task body. Anything defined in the context of a task body for a PPL task works identically in the context of the function object passed to create_async in similar circumstances.

 task<int> t( []() -> int {
     return 42;
 });


Actions, Operations, and Progress

The Windows Runtime API divides asynchronous constructs into four different interfaces depending on whether the asynchronous construct produces a result or not and whether the asynchronous construct provides intermediate progress reporting or not:

 

No intermediate progress

Reports progress of type U

No result

IAsyncAction

IAsyncActionWithProgress<U>

Produces a result of type T

IAsyncOperation<T>

IAsyncOperationWithProgress<T, U>

 

create_async behaves similarly. The return type of create_async is governed by the signature of the function object passed to it. Function objects which return void produce an action. Those that return a given type T will produce an operation of T. Similarly, progress reporting is accomplished through an object that the PPL passes to the function object. A function object which takes a progress_reporter<U> as its sole parameter will produce progress reports of type U. A function object which takes no parameters will not produce progress reports. The below summarizes this:

 

(No Progress)

WithProgress<U>

Action

[]() -> void { … }

[](progress_reporter<U>) -> void { … }

Operation<T>

[]() -> T { … }

[](progress_reporter<U>) -> T { … }

 

As an example, a simple operation with progress (which asynchronously computes the sum of values 1 through n) is shown below:

 //
 // Report the sum of 1..n with intermediate progress as a percentage done:
 //
 auto op = create_async( [n](progress_reporter<float> reporter) -> int {
     int computedValue = 0;
     for (int i = 1; i <= n; ++i)
     {
         float pctDone = i * (100.0 / n);
         reporter.report(pctDone);
  
         computedValue += i;
     }
     return computedValue;
 });


Tasks and Return Types

Up to now, the snippets that we’ve looked at for create_async have all passed function objects which return some type T (or void). While this is the easiest way to create an asynchronous operation using create_async, it is not the only way. In a similar fashion to the way that continuations treat returns of task<T> specially (unwrapping the task to flatten a continuation chain), create_async also treats return types of task<T> specially. When such a task is returned, it is the task which represents the asynchronous operation which is created. The below snippet behaves identically to the first create_async example we showed:

 IAsyncOperation<int>^ asyncOp = create_async( []() -> task<int> {
     return task<int>( [](){
         return 42;
     });
 });

 

This is true for all the signatures which create_async accepts. We can extend the signature table shown above with four new signatures:

 

(No Progress)

WithProgress<U>

Action

[]() -> task<void> { … }

[](progress_reporter<U>) -> task<void> { … }

Operation<T>

[]() -> task<T> { … }

[](progress_reporter<U>) -> task<T> { … }


Composition

What is the benefit of treating the return of a task<T> specially here? Composition!

Since any potentially long running API in the Windows Runtime is exposed as an asynchronous construct, you will often find yourself composing the results of multiple asynchronous operations to complete work. This is typically done using a task continuation chain. Consider a slight refactoring of the example of writing a string to a file as shown in the prior blog post:

  
 task<bool> WriteTextToFile()
 {
     std::shared_ptr<IOutputStream^> outputStream = 
         std::make_shared<IOutputStream^>(nullptr); // avoid capturing locals
  
     StorageFolder^ item = Windows::Storage::KnownFolders::PicturesLibrary;
  
     auto op = item->CreateFileAsync("myfile.txt");
  
     task<StorageFile^> createFileTask(op);
  
     return createFileTask.then([](StorageFile^ storageFile) {
         return storageFile->OpenAsync(FileAccessMode::ReadWrite);
     }).then([outputStream](IRandomAccessStream^ rastream) -> 
             IAsyncOperation<unsigned int>^ {
         *outputStream = rastream->GetOutputStreamAt(0);
         DataWriter^ bbrw = ref new DataWriter(*outputStream);
         bbrw->WriteString("Hello async!");
         return bbrw->StoreAsync();
     }).then([outputStream](task<unsigned int> writtenTask) {
         return (*outputStream)->FlushAsync();
     });
 }
  
 task<bool> writeFileTask = WriteTextToFile();
 writeFileTask.then([](task<bool> task_flushed) {
     // This is the error-handling continuation
     try
     {
         bool flushed = task_flushed.get();
         // Report success
     }
     catch (std::exception& ex)
     {
         // Report failure
     }
 });

 

How might we expose this aggregate operation as a single asynchronous operation that someone else can consume? Wrap it in create_async and return the last task in the continuation chain!

 IAsyncOperation<bool>^ aggregateOp = create_async( []() -> task<bool> {
     return WriteTextToFile();
 });

 

Clearly, if we were writing this for production purposes, the end result of the aggregate operation would be something more meaningful than simply the result of the flush operation and some additional things would be parameterized; however – the principle remains: the task<T> forms of create_async allow powerful forms of composition! Your code will be in control of what task or what continuation the asynchronous operation represents. This allows arbitrary forms of composition as the task being returned can be any PPL task, including the end result of a continuation chain!

What’s more is that any of the interfaces returned from create_async are Windows Runtime interfaces and can be used across the spectrum of languages that the Windows Runtime supports. We can expose this as an API that C# or JavaScript can consume.

The PPL tasks sample pack has been updated to include a header only implementation of create_async in the ppltasks.h header and numerous samples which highlight its usage in a cross language manner between C++ and JavaScript. Two samples of note in this regard are the ImageTransformer sample and the NQueens sample. ImageTransformer is a sample which exposes an asynchronous operation in C++ (using create_async) which “cartoonizes” an image and a front-end which utilizes this asynchronous API from JavaScript. NQueens is a sample which exposes an asynchronous operation in C++ (again using create_async) which is a naïve brute force parallel solver for the N-Queens problem and a front-end which utilizes this asynchronous API from JavaScript to present a graphical view of the solutions. Both of these samples demonstrate create_async and its composition with other parallel constructs in the PPL. Try it now and let us know what you think!

Comments

  • Anonymous
    October 27, 2011
    My previous comment did not 'take'. I will try again.  This is elegant, beautiful. As someone just learning C++, I can understand this. Thank you and your team. Keep it comming. Just beautiful.

  • Anonymous
    December 09, 2011
    It's great to see the scripting, managed and native worlds finally start colliding in a meaningful way on Windows 8. The same concepts are utilized throughout the whole operating system, which allows developers to really choose the languages that best suit their purpose.

  • Anonymous
    March 05, 2012
    The link to the PPL Sample Pack open a page where the content is not published (This item is not yet published.) code.msdn.microsoft.com/.../Windows-8-Asynchronous-08009a0d

  • Anonymous
    April 15, 2014
    "This is elegant, beautiful. As someone just learning C++" The latter explains your view of the former. It in actuality ppl in c++ is anything but.