在這一章中,我們討論一下如何創建和使用一個異步的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
