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里面使用。
// Cancel button event handler:
fileTaskTokenSource.cancel();
// task chain
task<StorageFile^> getFileTask(storageFolder->GetFileAsync(), fileTaskTokenSource.get_token());
4. 错误的捕获要在.then的方法里面进行
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 |
T or task<T> |
(progress_reporter) |
(progress_reporter,cancellation_token) |
这里顺便要说的,create_async可以返回task<void>或者直接返回void,返回void的时候,lambda表达式里面的代码会在后台执行,同时这个async的方法可以被其他语言调用。当我们这段表达式里面含有异步操作的时候,我们就要用返回task,比如
return create_task(FirstAsync(...))
.then( [](X val){
return SecondAsync(val, ...);
}).then( [](Y val)
return ThirdAsync(val, ...);
});
});
这段代码会执行FirstAsync->SecondAsync->ThirdAsync,最后返回的是 ThirdAsync的结果。
我们可以创建一个有返回值,有取消操作,有进度监视器,可以抛出错误的 IAsyncActionOperationWithProgress 函数如下
{
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
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