2018年12月27日 星期四

Concurrency and asynchronous operations with C++/WinRT

簡單紀錄一下Concurrency and asynchronous operations with C++/WinRT

The Windows::Foundation Windows Runtime namespace contains four types of asynchronous operation object.
- IAsyncAction,
- IAsyncActionWithProgress,
- IAsyncOperation, and
- IAsyncOperationWithProgress.

在使用asynchronous Windows function時有2種方式
- blocking
- non-blocking
blocking適合用在console program或是background thread,不適合用在UI thread.


*********************************
coroutine介紹
*********************************
C++/WinRT 整合了 C++ coroutines into the programming model to provide a natural way to cooperatively wait for a result.
只要在function前宣告成上述提到的4種asynchronous operation object,即為coroutines。如下ProcessFeedAsync

IAsyncAction ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;
    SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
    print_function();
}

下例即為如何呼叫此coroutine:
int main
{
    auto processOp{ ProcessFeedAsync() };
    processOp.get();
}

MS對coroutine的解釋:
A coroutine is a function that can be suspended and resumed. In the ProcessFeedAsync coroutine above, when the co_await statement is reached, the coroutine asynchronously initiates the RetrieveFeedAsync call and then it immediately suspends itself and returns control back to the caller (which is main in the example above). main can then continue to do work while the feed is being retrieved and printed. When that's done (when the RetrieveFeedAsync call completes), the ProcessFeedAsync coroutine resumes at the next statement.


*********************************
coroutine回傳值
*********************************
coroutine可以回傳Windows Runtime type,如上。也可以回傳非Windows Runtime type,如concurrency::task. 範例如下
concurrency::task RetrieveFirstTitleAsync()
{
    return concurrency::create_task([]
    {
        Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
        SyndicationClient syndicationClient;
        SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
        return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };
    });
}

int main()
{
    winrt::init_apartment();

    auto firstTitleOp{ RetrieveFirstTitleAsync() };
    // Do other work here.
    std::wcout << firstTitleOp.get() << std::endl;
}


*********************************
coroutine參數
*********************************
如果需要在coroutine中傳遞參數,要用const,以確保thread safe.


*********************************
coroutine的thread
*********************************
coroutine在碰到co_await, co_return, or co_yield時會rutern。
但如果在coroutine裡面沒有機會使用到這幾個時,可以直接使用
co_await winrt::resume_background();
這樣可以讓自己被丟到system thread pool,返回caller的thread,然後讓system啟動一個thread來完成自己的coroutine.

然而使用co_await winrt::resume_foreground(UI_thread)可以讓自己的thread回到UI thread,這個方式可以用來update UI.

值得注意的是在suspension點之後,原來的thread可能會消失不見,resume之後可能會是另一條thread來執行。但是如果我們使用的是Windows Runtime type (IAsyncXXX)的話,C++/WinRT會自動幫我們處理。C++/WinRT會在suspension之前先紀錄calling context,等到resume時會檢查並確保是在calling context. 如果不是使用(IAsyncXXX)的話,請記得確認此library有提供這個技術。


*********************************
Canceling an asychronous operation, and cancellation callbacks
*********************************
使用 IAsyncXXX.Cancel()來取消一個async operation.
或用 IAsyncXXX.callback()來註冊取消一個async operation之後要執行的function.

*********************************
Reporting progress
*********************************
使用 IAsyncActionWithProgress或IAsyncOperationWithProgress來回報進度。

*********************************
Fire and forget
*********************************
宣告coroutine為winrt::fire_and_forget,即為不等待結束。

The visual studio behavior with WinRT runtime component

When "windows runtime component" project created with winrt type.
1. VS default create Class.idl, Class.h, Class.cpp under root of project folder.
2. when project built, VS will generate some files in "Generated Files" folder.
    - Class.g.h
    - module.g.cpp
    - folder "winrt"
        - $project_name.h          // define template for Class
        - folder "impl"
            - $project_name.0.h    // define template for $project_name.1.h
            - $project_name.1.h    // define IClass for $project_name.2.h
            - $project_name.2.h    // define Class for $project_name.h
        - folder "source"
            - Class.h              // this file content is same as default created Class.h
            - Class.cpp            // this file content is same as default created Class.cpp

3. When change members in Class.idl and rebuild project.
3.1. files under root project folder.
    - Class.h                      // not re-generated.
    - Class.cpp                    // not re-generated.
3.2. files under "Generated Files" folder.
    - Class.g.h                    // re-generated, not content is not affected.
    - module.g.cpp                 // re-generated, not content is not affected.
    - files under "Generated Files/winrt" folder.
        - $project_name.h          // re-generated, add members.
        - files under "Generated Files/winrt/impl" folder.
            - $project_name.0.h    // re-generated, add members.
            - $project_name.1.h    // re-generated, not content is not affected.
            - $project_name.2.h    // re-generated, not content is not affected.
        - files under "Generated Files/winrt/source" folder.
            - Class.h              // re-generated, add members.
            - Class.cpp            // re-generated, add members.

4. When rename class name in Class.idl
4.1. files under root project folder.
    - Class.h                      // not re-generated.
    - Class.cpp                    // not re-generated.
4.2. files under "Generated Files" folder.
    - Class.g.h                    // not re-generated. just because new class file generated.
    - NewClass.g.h                 // new class file generated.
    - module.g.cpp                 // re-generated, content changed.
    - files under "Generated Files/winrt" folder.
        - $project_name.h          // re-generated, content changed to new class related.
        - files under "Generated Files/winrt/impl" folder.
            - $project_name.0.h    // re-generated, content changed to new class related.
            - $project_name.1.h    // re-generated, content changed to new class related.
            - $project_name.2.h    // re-generated, content changed to new class related.
        - files under "Generated Files/winrt/source" folder.
            - Class.h              // not re-generated. just because new class file generated.
            - Class.cpp            // not re-generated. just because new class file generated.
            - NewClass.h           // new class file generated.
            - NewClass.cpp         // new class file generated.