温故而知新系列--Windows 8 的异步编程(二)可爱的task和then(await)
这是Windows 8 异步编程的第二部分,为什么分成两节呢,因为,第二节确实太让人兴奋了。恰巧有篇Windows 8 开发人员博客也是这样一个结构,题目是WinRT 和 await,之前看过,没有什么感觉,今天试了一下,里面还是有很多需要注意的地方。
参考文献:
1. Windows 8 应用程序开发人员博客:深入探究WinRT和await: http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/04/30/winrt-await.aspx
2. MSDN
因为我使用的是C++语言,所以我主要是用C++来编程,C#开发的朋友可以看参考文献1,以下是我的理解和我觉得重要的东西。请原谅我有一部分内容是摘抄自文献1.
一。 基础知识
我也觉得应该从基础着手,WinRT中的所有异步功能全部源自一个接口:Windows::Foundation::IAsyncInfo
1
public
interface IAsyncInfo
2 {
3 AsyncStatus Status { get; } // 只读
4 HResult ErrorCode { get; } // 只读
5 uint Id { get; } // 只读
6
7 void Cancel();
8 void Close();
9 }
所有的异步操作都应该实现此接口。但是该接口缺少了一个至关重要的功能:操作完成之后的回调函数。因此就有了下面四个接口。我想说,请大家注意每个新街口中都有一个Completed的属性,我们可以设置这个属性所要执行的代码,就是我们的操作完成之后,将要执行的代码。
2 {
3 AsyncStatus Status { get; } // 只读
4 HResult ErrorCode { get; } // 只读
5 uint Id { get; } // 只读
6
7 void Cancel();
8 void Close();
9 }
1 public interface IAsyncAction : IAsyncInfo
2 {
3 AsyncActionCompletedHandler Completed { get; set; }
4 void GetResults(); // void,不返回结果
5 }
6
7 public interface IAsyncOperation<TResult> : IAsyncInfo
8 {
9 AsyncOperationCompletedHandler<TResult> Completed { get; set; }
10 TResult GetResults(); // TResult 返回结果
11 }
12
13 public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
14 {
15 AsyncActionWithProgressCompletedHandler<TProgress> Completed { get; set; }
16 AsyncActionProgressHandler<TProgress> Progress { get; set; } // 有Progress
17 void GetResults(); // void,不返回结果
18 }
19
20 public interface IAsyncOperationWithProgress<TResult, TProgress> : IAsyncInfo
21 {
22 AsyncOperationWithProgressCompletedHandler<TResult, TProgress> Completed { get; set; }
23 AsyncOperationProgressHandler<TResult, TProgress> Progress { get; set; } // 有Progress
24 TResult GetResults(); // TResult,返回结果
25 }
3 AsyncActionCompletedHandler Completed { get; set; }
4 void GetResults(); // void,不返回结果
5 }
6
7 public interface IAsyncOperation<TResult> : IAsyncInfo
8 {
9 AsyncOperationCompletedHandler<TResult> Completed { get; set; }
10 TResult GetResults(); // TResult 返回结果
11 }
12
13 public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
14 {
15 AsyncActionWithProgressCompletedHandler<TProgress> Completed { get; set; }
16 AsyncActionProgressHandler<TProgress> Progress { get; set; } // 有Progress
17 void GetResults(); // void,不返回结果
18 }
19
20 public interface IAsyncOperationWithProgress<TResult, TProgress> : IAsyncInfo
21 {
22 AsyncOperationWithProgressCompletedHandler<TResult, TProgress> Completed { get; set; }
23 AsyncOperationProgressHandler<TResult, TProgress> Progress { get; set; } // 有Progress
24 TResult GetResults(); // TResult,返回结果
25 }
暂停,我想说,C#开发人员真的很幸运,C++开发人员需要写很久的代码,C#只要一点点就足够了。这是我的牢骚,请忽略。
那么我们还是以打开文件为例吧,上一篇文章中,我们使用了create_task这个方法,打开了文件,并且将一些内容显示在一个OutputTextBlock中,现在我们不使用task类,只使用我们的IAsyncOperation来做。
好,下面是C++同学的时间,请问,你能写出completed这个event的代码么?并保证其运行。
那开始吧。要知道FileOpenPicker::PickSingleFileAsync()返回的是一个Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^类型的操作:
Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ operation = openPicker->PickSingleFileAsync();
//
为了让大家看清楚类型,就没有用auto简写,大家用auto的话会非常简单
对应上面的四个接口,显然,返回的是第二个接口IAsyncOperation<T TResult>^操作,那么它有一个Windows::Foundation::AsyncOperationCompletedHandler<TResult>^ 类型的Completed事件,那么我们的completed就应该写成这样:
operation->Completed =
ref
new Windows::Foundation::AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>(
//
暂时省略);
好复杂!
还没完,继续。AsyncOperationCompletedHandler是一个Delegate,这个Delegate其实就是原来的函数指针封装了一下,既然是函数指针,那么肯定有参数,怎么寻找这个参数呢?把鼠标移到这个方法上,按下F12就会谈到Object Browse里面去,你就会发现这个Delegate,它有一个Invoke(。。。)方法,里面就是它的参数了。
另外,我们没使用+=操作符,而是使用了=,为什么呢?因为Completed是个属性,不是个event。。。
回调函数的代码段,我们用Lambda表达式来写:
1 operation -> Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler < Windows::Storage::StorageFile ^> ([ this ](Windows::Foundation::IAsyncOperation < Windows::Storage::StorageFile ^>^ asyncInfo, Windows::Foundation::AsyncStatus status)
2 {
3 // 这里,貌似程序会在这里执行3次,具体原理不太清楚。
4 if(status == Windows::Foundation::AsyncStatus::Completed)
5 {
6 // 因为执行3次,所以我在这个里面加了一个计数器,但是最终的结果还是1,这个异步方法还是执行了一次
7 // 这里为什么是要Dispatcher->RunAsync呢,因为,这是一个回调函数,另一个线程中了。
8 // 可以看到,这所有的一切都可以用then或者await来代替。
9 this->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, ref new Windows::UI::Core::DispatchedHandler([ this](){
10 static int i = 0;
11 i++;
12 OutputTextBlock->Text = i.ToString();
13 }));
14 }
15 // 如果执行了operation->Canceled()方法会到这里
16 else if(status == Windows::Foundation::AsyncStatus::Canceled)
17 {
18 this->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, ref new Windows::UI::Core::DispatchedHandler([ this](){
19 OutputTextBlock->Text = "Canceled";
20 }));
21 }
22 // 操作出现错误会到这里
23 else if(status == Windows::Foundation::AsyncStatus::Error)
24 {
25 this->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, ref new Windows::UI::Core::DispatchedHandler([ this](){
26 OutputTextBlock->Text = "Error";
27 }));
28 }
29 // 这里不能返回StorageFile^ 类型的对象,为神马呢?因为TResult是void,如果你返回了,那么编译器会提醒你很长一段东西,你自己就会发现了
30 // return safe_cast<Windows::Storage::StorageFile^>(asyncInfo->GetResults());
31 });
3 // 这里,貌似程序会在这里执行3次,具体原理不太清楚。
4 if(status == Windows::Foundation::AsyncStatus::Completed)
5 {
6 // 因为执行3次,所以我在这个里面加了一个计数器,但是最终的结果还是1,这个异步方法还是执行了一次
7 // 这里为什么是要Dispatcher->RunAsync呢,因为,这是一个回调函数,另一个线程中了。
8 // 可以看到,这所有的一切都可以用then或者await来代替。
9 this->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, ref new Windows::UI::Core::DispatchedHandler([ this](){
10 static int i = 0;
11 i++;
12 OutputTextBlock->Text = i.ToString();
13 }));
14 }
15 // 如果执行了operation->Canceled()方法会到这里
16 else if(status == Windows::Foundation::AsyncStatus::Canceled)
17 {
18 this->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, ref new Windows::UI::Core::DispatchedHandler([ this](){
19 OutputTextBlock->Text = "Canceled";
20 }));
21 }
22 // 操作出现错误会到这里
23 else if(status == Windows::Foundation::AsyncStatus::Error)
24 {
25 this->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, ref new Windows::UI::Core::DispatchedHandler([ this](){
26 OutputTextBlock->Text = "Error";
27 }));
28 }
29 // 这里不能返回StorageFile^ 类型的对象,为神马呢?因为TResult是void,如果你返回了,那么编译器会提醒你很长一段东西,你自己就会发现了
30 // return safe_cast<Windows::Storage::StorageFile^>(asyncInfo->GetResults());
31 });
具体的内容都是上面的这段代码了,有些东西我想再提醒一下。
1. 首先,我们来看第30行,因为我觉得这个方法是返回一个StorageFile对象的,所以我在Lambda方法中理应返回它,但是如果返回的话,就是错误的,为什么?原因是这里的返回值实际上是Invoke方法的返回值,在这里这个返回值是void的,所以你不能返回任何东西。
2. 使用asyncInfo->GetResults();可以得到你的结果,回头看看那四个接口,有些返回TResult,有些返回void,是吧。
3. 在这个代码段里,我们已经跑到了另外一个线程中去了,如果你想操作UI,那么,你必须使用Dispatcher->RunAsync方法调度回UI线程,不信你可以试试。
4. 你可以在代码中判断现在IAsyncOperation的状态。
哦,看看C#程序员的优越性把:
op.Completed = (info, status) =>
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
else if (status == AsyncStatus.Error)
{
// Error occurred, Report error
}
};
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
else if (status == AsyncStatus.Error)
{
// Error occurred, Report error
}
};
希望你还能坚持。*^◎^*
看到这里你发现了么?这段代码的功能其实是跟上一篇代码的功能一模一样,再把之前的代码贴过来
1 create_task(openPicker->PickSingleFileAsync()).then([
this](StorageFile^ file)
2 {
3 if (file)
4 {
5 OutputTextBlock->Text = "Picked photo: " + file->Name;
6 }
7 else
8 {
9 OutputTextBlock->Text = "Operation cancelled.";
10 }
11 }).then([ this](task< void> t)
12 {
13 try{
14 t. get();
15 } catch(Platform::Exception^ e)
16 {
17 OutputTextBlock->Text = e->Message
18 }
19 });
2 {
3 if (file)
4 {
5 OutputTextBlock->Text = "Picked photo: " + file->Name;
6 }
7 else
8 {
9 OutputTextBlock->Text = "Operation cancelled.";
10 }
11 }).then([ this](task< void> t)
12 {
13 try{
14 t. get();
15 } catch(Platform::Exception^ e)
16 {
17 OutputTextBlock->Text = e->Message
18 }
19 });
额,希望你能明白,task为我们做了些什么东西。我来总结一下吧:
1. 没有了复杂的completed回调函数,操作完成之后,执行then中的代码,简单而明了。
2. then中的异步代码是在UI线程中的,没有Dispatcher->RunAsync方法,不是么?
3. 可以有多个then
4. 可以处理异常,不是更强大么?
task为我们做的,就是从30多行复杂的代码精简到只有20行。
至于原理,大家可以阅读参考文献1,我暂时还没有怎么理解。。。