Search code examples
uwptaskc++-cxppl

How does create_async behave on the UI thread?


Let's say I have a function that I call on the UI thread:

IAsyncOperation<BitmapImage^>^ GetImage()
{
    return create_async([]
    {
        return create_task(GetStreamAsync())
            .then([] (Stream^ stream)
        {
            auto image = ref new BitmapImage();
            // set image properties.
            image.SetSourceAsync(stream);
            return image;
        });
    });
}

I know the inner task will also execute on the UI thread since this question's answer indicates an apartment aware task continues in its apartment by default.

But when is the execution of the create_task body scheduled? Does it end up happening synchronously? Or does the UI thread wait until it has free cycles to process the async logic?


Solution

  • Let's find out:

    void ShowThread(std::string caller)
    {
      std::string result{ caller + ": Current thread is " };
      result = result + std::to_string(GetCurrentThreadId());
      result = result + "\r\n";
    
      OutputDebugStringA(result.c_str());
    }
    
    using namespace Windows::Foundation;
    using namespace Windows::Storage;
    
    IAsyncOperation<IStorageItem^>^ GetFile()
    {
      ShowThread("GetFile");
      return create_async([]
      {
        ShowThread("Inside create_async");
        return create_task(
          ApplicationData::Current->LocalFolder->TryGetItemAsync(L"foo"))
          .then([](IStorageItem^ item)
          {
            ShowThread("Inside .then");
            return item;
        });
      });
    }
    
    MainPage::MainPage()
    {
      InitializeComponent();
      GetFile();
    }
    

    This shows something like this:

    GetFile: Current thread is 34100
    Inside create_async: Current thread is 34100
    Inside .then: Current thread is 34100
    

    You can see they're all on the same thread - create_async calls its argument immediately (it doesn't schedule it as a task) and simply converts the return value to an IAsyncOperation<T>. If you want the continuation to not be on the UI thread, you can do this:

        return create_task(
          ApplicationData::Current->LocalFolder->TryGetItemAsync(L"foo"))
          .then([](IStorageItem^ item)
        {
          ShowThread("Inside .then");
          return item;
        }, task_continuation_context::use_arbitrary());
    

    Then PPL will run the continuation on an arbitrary thread.