C++异步编程 for VS2011(四)

在这一章中,我们讨论一下如何创建和使用一个异步的WinRT API。

在WinRT中有四种异步的接口,IAsyncAction 没有返回值类型的接口,IAsyncActionWithProgress<TProgress> 没有返回值类型,但是有进度监视器的接口。IAsyncOperation<TResult> 返回值为T的接口,IAsyncActionOperationWithProgress<TResult,TProgress> 返回值为T,有进度监视器的接口。

 之前我们说过,在WinRT中可以使用task来调用IAsyncAction 等接口,但是有一些其他的功能被限制,比如:

1. 在WinRT中task只能用.then来加入前面的task执行成功之后的行为。task.wait函数不可以使用。

2. progress_reporter不能在task里面使用。

3. cancellation_taken,可以作为传入参数在task里面使用。

cancellation_token_source fileTaskTokenSource;

// Cancel button event handler:
fileTaskTokenSource.cancel();

// task chain

task<StorageFile^> getFileTask(storageFolder->GetFileAsync(), fileTaskTokenSource.get_token());

4. 错误的捕获要在.then的方法里面进行

    StorageFolder^ documentsFolder = KnownFolders::DocumentsLibrary;
    task<StorageFile^> getFileTask(documentsFolder->GetFileAsync(fileName));

    getFileTask.then([](StorageFile^ storageFileSample) {       
        return storageFileSample->DeleteAsync();
    }).then([](task<void> t) {

        try
        {
            t.get();

            // .get() didn't throw, so we succeeded.
            OutputDebugString(L"File deleted.");
        }
        catch (Exception^ e)
        {
                 //Example output: The system cannot find the specified file.
                 OutputDebugString(e->Message->Data());
        }
        

    });

 上面的部分说了一些在用IAsyncAction的注意事项。接着我们讲讲如何写IAsyncAction等方法。

我们需要用 create_async去创建IAsyncAction等返回值类型的异步操作。关于create_async的传入参数和返回值,可以参照下面的表格

To create this Windows Runtime interface

Return this type fromcreate_async

Pass these parameter types to your work function to use an implicit cancellation token

Pass these parameter types to your work function to use an explicit cancellation token

IAsyncAction

void ortask<void>

(none)

(cancellation_token)

IAsyncActionWithProgress<TProgress>

void ortask<void>

(progress_reporter)

(progress_reporter,cancellation_token)

IAsyncOperation<TResult>

T or task<T>

(none)

(cancellation_token)

IAsyncActionOperationWithProgress<TResult
s, TProgress>

T or task<T>

(progress_reporter)

(progress_reporter,cancellation_token)

这里顺便要说的,create_async可以返回task<void>或者直接返回void,返回void的时候,lambda表达式里面的代码会在后台执行,同时这个async的方法可以被其他语言调用。当我们这段表达式里面含有异步操作的时候,我们就要用返回task,比如

IAsyncOperation<T>^ asyncOperation = create_async( []() {
    return create_task(FirstAsync(...))
    .then( [](X val){
        return SecondAsync(val, ...);
    }).then( [](Y val)
        return ThirdAsync(val, ...);
    });

});

这段代码会执行FirstAsync->SecondAsync->ThirdAsync,最后返回的是 ThirdAsync的结果。

我们可以创建一个有返回值,有取消操作,有进度监视器,可以抛出错误的 IAsyncActionOperationWithProgress 函数如下

IAsyncOperationWithProgress<int,String^>^ TestWithProgressAsync(int input)
{
    return create_async([input](progress_reporter<String^> reporter) {
        if(input==0)
             throw ref new InvalidArgumentException();
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (is_task_cancellation_requested()) {                
                // Cancel the current task.
                cancel_current_task();
                return 32;
                moreToDo = false;
            }
            else {
                // Perform work.
                reporter.report("Running");
            }
        }
        reporter.report("Finished");
        return 42;
    });

progress_reporter<T> reporter 作为传入参数,这个T可以是各种类型,[input] 是为了绑定传入参数,is_task_cancellation_requested()捕获取消事件, reporter.report(T)汇报进度。

接着我们演示在WinRT中如何使用这个异步函数,使用这个异步函数有两种方式,一是通过task 封装,就如同文章开头所说,二是通过调用纯WinRT接口。

首先,我们演示一下如何用task的方式去调用这个异步函数。

首先,声明和初始化一个task和cancellation

 // In BlankPage.xaml.h

        cancellation_token_source TaskTokenSource;
        task<int> t;

// In BlankPage.xaml.cpp

BlankPage::BlankPage()
{
    InitializeComponent();
    // TestWithProgressAsync(0) will throw a execption
    t=task<int>(TestWithProgressAsync(1),TaskTokenSource.get_token());
}

接着,拖一个cancel和except按钮还有一个名字是result 的TextBlock,在两个按钮的click事件中写入:

 void Application3::BlankPage::Button_Click_Cancel(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)

{
    
    
    cancellation_token_registration cookie;
    auto token =TaskTokenSource.get_token();
     cookie = token.register_callback([this, token, &cookie]() {
         safe_cast<TextBlock^>(this->FindName("result"))->Text+="Cancel";
        // Although not required, demonstrate how to unregister 
        
// the callback.
        token.deregister_callback(cookie);
    });
    TaskTokenSource.cancel();
}


void Application3::BlankPage::Button_Click_Except(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
    int c=0;
    t.then([this,&c](task<int> t){
        try
        {
        
            int res=t.get();

    c+=res;   

            safe_cast<TextBlock^>(this->FindName("result"))->Text+=res;
        }catch(Exception^ ex)
        {
            safe_cast<TextBlock^>(this->FindName("result"))->Text+=ref new String(ex->Message->Data());
        }
    },task_continuation_context::use_arbitrary());
}

这里要注意的几点是:

1. task里面无法使用进度监视器。

2. 我们可以通过注册token的callback 函数来处理cancel之后的行为,在不改变原始函数的情况下。上面的例子中,我们用callback函数来修改TextBlock里面的内容。

3. 我们只有在 t.then([this](task<int> t) 这种情况下才可以使用t.get(),如果我们直接使用Application3::BlankPage::t.get()会报错。

4. 如果我们在想使用.then 外面的变量,比如Button_Click_Except函数里面的 int c,我们不仅仅需要绑定[this,&c](task<int>, 我们还需在then方法里面加入task_continuation_context::use_arbitrary()这个参数。这是因为XAML的UI线程都是STA的,而Lambda表达式是MTA,所以两者之间共享数据默认是不可以的。所以就需要task_continuation_context::use_arbitrary()这个参数来修改这个默认行为。

我们再来看看如何用WinRT的namespace里面的接口来调用这个异步函数。

首先,要声明一个 Windows::Foundation::IAsyncOperationWithProgress<int,Platform::String^>^ AsyncOper; 在头文件里,这里注意如果没有引用Platform命名空间的话,一定要在Sting^前面加上这个命名空间,否则编译会报错。

然后在cpp文件里

 BlankPage::BlankPage()

{
    InitializeComponent();
    // TestWithProgressAsync(0) will throw a execption
    AsyncOper=TestWithProgressAsync(0);

    AsyncOper->Progress=ref new AsyncOperationProgressHandler<int,String^>(
        [this](IAsyncOperationWithProgress<int,String^>^ pretask,  String^ progressInfo){
            safe_cast<TextBlock^>(this->FindName("result"))->Text+=progressInfo+"\n";

    });

    AsyncOper->Completed=ref new AsyncOperationWithProgressCompletedHandler<int,String^>(
        [this](IAsyncOperationWithProgress<int,String^>^ pretask, AsyncStatus asyncStatus){
            if(asyncStatus==AsyncStatus::Completed)
            {
                int res=pretask->GetResults();
            }
            if(asyncStatus==AsyncStatus::Canceled)
            {
                safe_cast<TextBlock^>(this->FindName("result"))->Text="Cancel";
            }
            if(asyncStatus==AsyncStatus::Error)
            {
                try
                {
                    int res=pretask->GetResults();

                }catch(Exception^ ex)
                {
                    safe_cast<TextBlock^>(this->FindName("result"))->Text+=ref new String(ex->Message->Data());
                }
            }
    });
}


void Application3::BlankPage::Button_Click_Cancel(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
    AsyncOper->Cancel();
}

我们通过AsyncOper->Progress=ref new AsyncOperationProgressHandler 来添加进度监视器

通过AsyncOper->Completed=ref new AsyncOperationWithProgressCompletedHandler 添加完成事件的委托,然后我们判断asyncStatus的三个状态,来做出不同的反应。

通过AsyncOper->Cancel()来取消异步操作。

这里面要强调的是错误的捕获要用类似于task的方法,既try-catch块。IAsyncInfo.ErrorCode 这个属性得到的值是未初始化的。

最后,本文介绍了如何写和如何调用一个WinRT的异步操作,希望这篇文章能给大家在使用WinRT异步API的时候提供一些帮助。 

引用自:

http://msdn.microsoft.com/en-us/magazine/hh781020.aspx 

http://msdn.microsoft.com/en-us/library/windows/apps/hh780559.aspx 

http://msdn.microsoft.com/en-us/library/windows/apps/hh750082.aspx 

 http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/8061242d-0e96-4d9d-8743-563291ef8088/

原文地址:https://www.cnblogs.com/zjjcy/p/2489298.html