locked
Project task<> from C++ to C#

    Question

  • Is it possible to project a method returning a task<> from C++ to C#, and wait for its completion on the C# side with the await keyword?

    I am using the FontLoader class from the DirectWrite magazine sample app (http://code.msdn.microsoft.com/windowsapps/Magazine-Sample-2a657289/sourcecode?fileId=44769&pathId=332910558), which has a LoadAsync method:

    task<void> FontLoader::LoadAsync() 
    { 
        // Locate the "fonts" sub-folder within the document folder 
        return task<void>([this]() 
        { 
            task<StorageFolder^>(m_location->GetFolderAsync("fonts")).then([=](StorageFolder^ folder) 
            { 
                // Enumerate a list of .TTF files in the storage location 
                auto filters = ref new Platform::Collections::Vector<Platform::String^>(); 
                filters->Append(".ttf"); 
     
                auto queryOptions = ref new QueryOptions(CommonFileQuery::DefaultQuery, filters); 
                auto queryResult = folder->CreateFileQueryWithOptions(queryOptions); 
     
                return queryResult->GetFilesAsync(); 
     
            }).then([=](IVectorView<StorageFile^>^ files) 
            { 
                m_fontFileCount = files->Size; 
     
                std::vector<task<IBuffer^>> tasks; 
     
                for (uint32 i = 0; i < m_fontFileCount; ++i) 
                { 
                    auto file = dynamic_cast<StorageFile^>(files->GetAt(i)); 
     
                    tasks.push_back(task<IBuffer^>(FileIO::ReadBufferAsync(file))); 
                } 
     
                return when_all(tasks.begin(), tasks.end()); 
     
            }).then([=](std::vector<IBuffer^> buffers) 
            { 
                for each (IBuffer^ buffer in buffers) 
                { 
                    auto fileData = ref new Platform::Array<byte>(buffer->Length); 
                    DataReader::FromBuffer(buffer)->ReadBytes(fileData); 
     
                    ComPtr<FontFileStream> fontFileStream(new FontFileStream(fileData)); 
                    m_fontFileStreams.push_back(fontFileStream); 
                } 
     
            }).wait(); 
        }); 
    } 

    Is it possible to project this method to C# and be able to await for its completion? I tried to simply return the task from a WinRT component, but I got the error that the public member of the WinRT component can not return the native type task<void>.

    Thanks in advance!


    • Edited by MarkVincze Monday, June 25, 2012 9:14 AM
    Monday, June 25, 2012 9:14 AM

Answers

  • Yes, it is possible to have a task<> projected as an IAsync* that can be consumed in a cross-language way.  We do not directly project task<> across the language boundary.  It's a C++ object.  There is an API in the concurrency namespace, create_async which is exactly for this purpose.  See the following documentation or this (somewhat older) blog entry.

    In short, you can either modify the LoadAsync method to return an IAsyncAction^ or have a wrapper function which is the version exposed across the language boundary which creates an IAsyncAction^ from the task<> that LoadAsync is returning.  In either case, create_async is what you want to project an async operation from C++ across the language boundary.

    Which of these methods you choose depends on your code and usage patterns.  There's somewhat a loss of efficiency if you use create_async to wrap a task and then immediately turn around and wrap the resulting async interface in a task to consume it, so if you use LoadAsync a lot within your own module, it's probably somewhat more efficient to have the task<> returning version for internal use and a wrapper which can be projected.

    Some short code examples:

    Modify the LoadAsync method to return an IAsyncAction^ and have it return the projectable interface directly: 

    IAsyncAction^ FontLoader::LoadAsync()
    {
        return create_async([=]()
        {
            task<StorageFolder^>(m_location->GetFolderAsync("fonts")).then([=](StorageFolder^ folder) 
            {
                // Remainder of code removed to shorten example.
                …
    
            }).wait();
        });
    }

    Wrap this method in one that returns the IAsync* interface which can be projected across the language boundary:

    IAsyncAction^ FontLoader::PublicLoadAsync() { return create_async([=]() { return LoadAsync(); }); } 

    • Proposed as answer by Jesse Jiang Tuesday, June 26, 2012 6:22 AM
    • Marked as answer by MarkVincze Tuesday, June 26, 2012 8:02 AM
    Monday, June 25, 2012 5:16 PM

All replies

  • Yes, it is possible to have a task<> projected as an IAsync* that can be consumed in a cross-language way.  We do not directly project task<> across the language boundary.  It's a C++ object.  There is an API in the concurrency namespace, create_async which is exactly for this purpose.  See the following documentation or this (somewhat older) blog entry.

    In short, you can either modify the LoadAsync method to return an IAsyncAction^ or have a wrapper function which is the version exposed across the language boundary which creates an IAsyncAction^ from the task<> that LoadAsync is returning.  In either case, create_async is what you want to project an async operation from C++ across the language boundary.

    Which of these methods you choose depends on your code and usage patterns.  There's somewhat a loss of efficiency if you use create_async to wrap a task and then immediately turn around and wrap the resulting async interface in a task to consume it, so if you use LoadAsync a lot within your own module, it's probably somewhat more efficient to have the task<> returning version for internal use and a wrapper which can be projected.

    Some short code examples:

    Modify the LoadAsync method to return an IAsyncAction^ and have it return the projectable interface directly: 

    IAsyncAction^ FontLoader::LoadAsync()
    {
        return create_async([=]()
        {
            task<StorageFolder^>(m_location->GetFolderAsync("fonts")).then([=](StorageFolder^ folder) 
            {
                // Remainder of code removed to shorten example.
                …
    
            }).wait();
        });
    }

    Wrap this method in one that returns the IAsync* interface which can be projected across the language boundary:

    IAsyncAction^ FontLoader::PublicLoadAsync() { return create_async([=]() { return LoadAsync(); }); } 

    • Proposed as answer by Jesse Jiang Tuesday, June 26, 2012 6:22 AM
    • Marked as answer by MarkVincze Tuesday, June 26, 2012 8:02 AM
    Monday, June 25, 2012 5:16 PM
  • Thanks!
    Tuesday, June 26, 2012 8:02 AM