使用 Rx.NET 进行事件处理与异步编程
1. 使用 Rx.NET 异步搜索 Flickr 照片
在这部分,我们将构建一个使用 Rx.NET 异步搜索 Flickr 照片的应用程序。通过这个示例,你将学习如何从事件创建可观察数据源,以及如何对这些数据源进行订阅。
1.1 创建 Windows Phone 项目
创建一个新的 Windows Phone 项目用于 Flickr 图片搜索,具体步骤如下:
1. 启动 Visual Studio 2010 Express for Windows Phone,创建一个新的 Windows Phone 应用程序项目,将其命名为
FlickrRx
。
2. 将应用程序名称更改为
Flickr Search
,页面标题更改为
Rx at Work
。具体操作是:选中应用程序名称,按下 F4,编辑
Text
属性,然后对页面标题进行相同操作。
3. 在解决方案资源管理器中右键单击项目名称,选择“添加引用”,添加
Microsoft.Phone.Reactive
和
System.Observable
程序集。
1.2 添加用户界面
为项目添加 UI 元素,UI 由一个文本框、一个标签和一个 WebBrowser 控件组成,步骤如下:
1. 从工具箱中选择一个文本框,拖放到设计界面上,将其重命名为
txtSearchTerms
。使文本框的宽度等于屏幕宽度,并清空
Text
属性。接着选择一个文本块,将其放置在文本框下方,重命名为
lblSearchingFor
,并将其宽度调整为屏幕宽度。
2. 从工具箱中选择 WebBrowser 控件,将其拖放到文本块下方的设计界面上,重命名为
webResults
,并使其宽度等于屏幕宽度。
1.3 添加搜索 Flickr 图片的逻辑
添加逻辑以使用 Flickr 图片搜索结果填充 WebBrowser 控件,步骤如下:
1. 打开
MainPage.xaml.cs
(右键单击
MainPage.xaml
并选择“查看代码”),在页面顶部粘贴以下
using
语句:
using Microsoft.Phone.Reactive;
-
在
MainPage()构造函数的InitializeComponent()语句之后立即粘贴以下代码:
var keys = Observable.FromEvent<KeyEventArgs>(txtSearchTerms, "KeyUp");
keys.Subscribe(evt =>
{
lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
});
上述代码中,第一行代码创建了一个可观察数据源
keys
,它包含了
txtSearchTerms
文本框的所有
KeyUp
事件。第二行代码是一个 lambda 表达式,它在这个集合上创建了一个观察者,并尝试使用文本框中输入的文本更新
lblSearchingFor
文本块。同时,它会显示代表使用文本框中提供的文本在 Flickr 上搜索结果的网页。
- 按下 F5 运行应用程序。当你输入第一个字符时,应该会看到 WebBrowser 控件尝试导航到 Flickr 搜索页面,将输入的唯一字符作为搜索条件。注意,几乎没有视觉指示表明后台正在进行搜索或导航操作。在后续部分,我们将创建一个动画,在 WebBrowser 控件加载图片搜索结果时播放。
2. 使用节流功能增强 Flickr 搜索
到目前为止,你可能会疑惑,除了观察者模式的复杂性,Rx.NET 为你的工具集增添了什么。其实到目前为止,Rx.NET 并没有带来太多不同,你使用标准的事件处理程序也能实现相同的功能。不过,在接下来的步骤中,Rx.NET 的强大之处将逐渐显现。
2.1 修改应用程序代码
对应用程序进行以下修改:
1. 将声明可观察集合的代码行从:
var keys = Observable.FromEvent<KeyEventArgs>(txtSearchTerms, "KeyUp");
修改为:
var keys = Observable.FromEvent<KeyEventArgs>(txtSearchTerms, "KeyUp").Throttle(TimeSpan.FromSeconds(.5));
- 将声明观察者的代码块从:
keys.Subscribe(evt =>
{
lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
});
修改为:
keys.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
if (txtSearchTerms.Text.Length > 0)
{
lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
}
});
-
按下 F5 运行应用程序。点击文本框,输入在 Flickr 上查找照片的搜索词(例如,输入
Barcelona),观察 WebBrowser 控件从 Flickr 检索那个美丽欧洲城市的图片。
2.2 代码分析
我们创建了一个可观察集合,它包含了
txtSearchTerms
文本框上生成的所有
KeyUp
事件。添加
Throttle(.5)
语句后,实际上是告诉 Rx.NET 只观察间隔超过半秒(0.5 秒)的
KeyUp
事件。假设普通用户按键间隔小于半秒,按键之间的半秒暂停告诉应用程序用户已准备好启动 Flickr 搜索,并准备“观察”其执行结果。
另外,在修改后的代码中,添加了逻辑,若文本框中未输入任何内容(例如用户使用退格键删除了内容),则不调用图片搜索。同时,使用了
ObserveOn(Deployment.Current.Dispatcher)
构造来帮助创建观察者。如果移除这个构造,代码将变为:
keys.Subscribe(evt =>
{
if (txtSearchTerms.Text.Length > 0)
{
lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
}
});
再次按下 F5 运行应用程序,会看到 Visual Studio 显示“Invalid cross-thread access”消息。这是因为在 .NET 平台上,从非 UI 线程更新 UI 是一个棘手的问题。Rx.NET 在后台创建了一个单独的线程,并从该线程将可观察数据源的更改通知推送给观察者,而这个后台线程不能直接修改 UI 线程。幸运的是,Rx.NET 的开发者提供了
ObserveOn()
扩展方法来解决这个问题,该方法可以接受一个
Dispatcher
对象,从而让你在 UI 线程上观察可观察数据源。
3. 添加 Flickr 图片加载时播放的动画
可以通过添加一个简单的动画来进一步增强 Flickr 图片搜索应用程序,该动画将在图片搜索结果网页加载时播放。具体步骤如下:
3.1 创建动画
-
仍然在 Windows Phone 的 Visual Studio 中,向手机设计界面添加一个文本块,将其放置在“Searching for”文本块和 WebBrowser 控件之间。将该文本块命名为
lblLoading,将标题设置为Loading Images,并将其Visibility属性设置为Collapsed。 -
要启动 Expression Blend 并加载 Flickr 项目,在 Visual Studio 中右键单击
MainPage.xaml并选择“在 Expression Blend 中打开”。Microsoft Expression Blend 将启动并打开你的解决方案,准备进行编辑。 -
在 Expression Blend 中,从工具箱中选择一个矩形,在
Loading Images文本块旁边绘制一个非常窄(几乎不可见)的矩形,并将其填充颜色设置为红色。 -
在“对象和时间线”窗口中,点击“新建”按钮,将故事板命名为
loadingImages,然后点击“确定”。 - 选择你放置在 Windows Phone 设计界面上的矩形,点击“记录关键帧”按钮。
- 将动画播放头(时间线中的黄色垂直线)移动到大约 1.5 秒处,再次点击“记录关键帧”按钮,然后将矩形的大小调整为接近手机屏幕的全宽。
-
在“对象和时间线”中,点击并选择
loadingImages故事板名称。出现故事板的“通用属性”对话框,在该故事板的“重复行为”属性中选择“永远”。 - 在 Expression Blend 中保存所有内容(文件 -> 全部保存),然后切换回 Visual Studio。
3.2 添加代码控制动画
-
为了在用户发起 Flickr 图片搜索时启动动画,调用
loadingImages动画的Begin方法。在MainPage.xaml.cs中,将创建KeyUp事件观察者的代码更改为:
keys.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
if (txtSearchTerms.Text.Length > 0)
{
lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
lblLoading.Visibility = System.Windows.Visibility.Visible;
loadingImages.Begin();
webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
}
});
-
一旦图片在 WebBrowser 控件中加载完成,通过调用
loadingImages动画的Stop方法停止动画。为此,使用 Rx.NET 订阅 WebBrowser 的Navigated事件。当此订阅接收到数据时,停止动画。在MainPage构造函数的末尾粘贴以下代码:
var browser = Observable.FromEvent<System.Windows.Navigation.NavigationEventArgs>(webResults, "Navigated");
browser.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
loadingImages.Stop();
lblLoading.Visibility = System.Windows.Visibility.Collapsed;
});
- 按下 F5,在文本框中输入一个关键字,观察图片加载到浏览器时的动画。
通过以上步骤,我们不仅实现了一个基本的 Flickr 图片搜索应用程序,还通过节流功能优化了搜索体验,并添加了动画增强了用户交互性。Rx.NET 的强大之处在于它可以将任何事件视为可观察数据源,无论是位置服务生成的坐标值、加速度计数据、按键事件还是 Web 浏览器事件。接下来,我们将继续学习 Rx.NET 的设计准则,并使用它构建一个简单的实时天气应用程序。
4. Rx.NET 设计准则
Rx.NET 已经有了自己的一套设计准则(可在 http://go.microsoft.com/fwlink/?LinkID=205219 查看),以帮助开发者在使用该库时做出最佳决策。下面介绍其中两个有用的策略。
4.1 考虑绘制弹珠图
如果你在 Microsoft 网站(特别是 Channel 9)上研究 Rx.NET,很可能会遇到所谓的弹珠图。弹珠图有助于理解输入的基于事件和异步的数据序列在应用给定的 Rx.NET 运算符后会发生什么。
例如,在一个接收手机地理位置的实际示例中,假设在用户输入新的感兴趣位置进行搜索之前,一直在收集手机用户的位置数据。最初,顶部的数据序列(
geoLocationReading
)接收数据点(每个数据点用一个小圆圈或弹珠表示)。然后,
newLocation
序列开始接收数据。应用
TakeUntil()
运算符后,结果序列(
result
)只会获取
geoLocationReading
序列中直到
newLocation
序列开始有数据点之前的数据点。之后,无论
geoLocationReading
或
newLocation
序列上出现多少数据点,结果序列都不会再获取额外的数据点。
通过在弹珠图上可视化 Rx.NET 运算符并理解它们产生的结果序列,你可以分析所有的 Rx.NET 运算符。此外,绘制你想要创建的序列的弹珠图,然后就可以确定需要使用哪些 Rx.NET 运算符来实现该序列。
4.2 考虑向引入并发的运算符传递特定的调度器
Rx.NET 可以通过抽象线程和并发的许多问题,并以声明式方式处理并发,从而使并发编程更简单。不过,即使有这种并发抽象,你仍然可以控制 Rx.NET 的执行方式以及 Rx.NET 通知的处理上下文,这就涉及到调度器。
在 Rx.NET 中,可以调度两件事:订阅的执行线程上下文和通知的发布线程上下文。这可以通过
IObservable<T>
接口的
SubscribeOn()
和
ObserveOn()
扩展方法来控制。这两个扩展方法都可以接受
Scheduler
静态类的属性,以下是可用的属性及其说明:
| 调度器属性 | 说明 |
| — | — |
|
Scheduler.Dispatcher
| 强制在拥有应用程序线程并内部维护工作项队列的
Dispatcher
上执行。 |
|
Scheduler.NewThread
| 将所有操作调度到一个新线程上。 |
|
Scheduler.ThreadPool
| 将所有操作调度到线程池上。 |
|
Scheduler.Immediate
| 确保操作立即执行。 |
|
Scheduler.CurrentThread
| 确保操作在进行原始调用的线程上执行。这与
Scheduler.Immediate
不同,因为在当前线程上调度的操作可能会排队等待稍后执行。 |
以下是在 Rx.NET 中使用调度器进行订阅的示例代码:
Observable.FromAsyncPattern<WebResponse>(
webRequest.BeginGetResponse,
webRequest.EndGetResponse)()
.SubscribeOn(Scheduler.NewThread)
.ObserveOn(Scheduler.Dispatcher)
.Subscribe(
Rx 设计准则认为,在适当的地方传递调度器是最佳实践,这样可以从一开始就在正确的位置创建并发。
5. 使用 Rx.NET 与 Web 服务异步检索天气数据
在这部分,我们将使用位于
www.webservicex.net/WS/WSDetails.aspx?CATID=12&WSID=56
的公共天气 Web 服务来检索并显示美国给定城市的当前天气。除了天气服务,该位置还提供许多其他有用的 Web 服务,包括邮政编码验证和货币转换。
从开发的角度来看,天气应用程序将包括异步捕获用户输入(城市名称)、异步调用 Web 服务,然后显示该城市的当前天气。接下来,我们将着手创建这个应用程序。
通过前面的学习,我们已经掌握了使用 Rx.NET 进行事件处理、优化搜索体验、添加动画以及遵循设计准则等知识。在构建天气应用程序的过程中,我们将进一步巩固这些知识,并学习如何处理错误恢复等概念。
下面是创建 Flickr 图片搜索应用程序的流程图:
graph LR
A[创建 Windows Phone 项目] --> B[添加用户界面]
B --> C[添加搜索逻辑]
C --> D[使用节流增强搜索]
D --> E[添加加载动画]
通过这个流程图,我们可以清晰地看到创建 Flickr 图片搜索应用程序的主要步骤。从项目创建开始,逐步添加界面、逻辑、优化功能和动画效果,最终完成一个功能丰富的应用程序。在后续构建天气应用程序时,我们也可以参考类似的流程,逐步实现各个功能模块。
使用 Rx.NET 进行事件处理与异步编程
6. 构建天气应用程序的准备工作
在构建天气应用程序之前,我们需要了解一些基本信息。该应用程序将使用位于
www.webservicex.net/WS/WSDetails.aspx?CATID=12&WSID=56
的公共天气 Web 服务,异步获取并显示美国给定城市的当前天气。
以下是构建该应用程序的大致步骤:
1. 创建项目:在 Visual Studio 中创建一个新的 Windows Phone 应用程序项目。
2. 添加引用:添加必要的引用,确保可以使用 Rx.NET 相关功能。
3. 设计界面:设计一个简单的界面,包含输入城市名称的文本框和显示天气信息的区域。
4. 实现逻辑:使用 Rx.NET 异步捕获用户输入的城市名称,调用 Web 服务获取天气数据,并将结果显示在界面上。
7. 具体实现步骤
7.1 创建项目
- 打开 Visual Studio 2010 Express for Windows Phone。
-
创建一个新的 Windows Phone 应用程序项目,命名为
WeatherApp。
7.2 添加引用
在解决方案资源管理器中,右键单击项目名称,选择“添加引用”,添加
Microsoft.Phone.Reactive
和
System.Observable
程序集,确保可以使用 Rx.NET 的功能。
7.3 设计界面
在
MainPage.xaml
中设计界面,示例代码如下:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBox x:Name="txtCityName" HorizontalAlignment="Left" Height="72" Margin="10,10,0,0" TextWrapping="Wrap" Text="Enter City Name" VerticalAlignment="Top" Width="460"/>
<TextBlock x:Name="lblWeatherInfo" HorizontalAlignment="Left" Margin="10,100,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="460"/>
</Grid>
上述代码创建了一个文本框
txtCityName
用于输入城市名称,以及一个文本块
lblWeatherInfo
用于显示天气信息。
7.4 实现逻辑
打开
MainPage.xaml.cs
,在页面顶部添加
using
语句:
using Microsoft.Phone.Reactive;
using System.Net;
在
MainPage()
构造函数中添加以下代码:
var cityInput = Observable.FromEvent<KeyEventArgs>(txtCityName, "KeyUp");
cityInput.Throttle(TimeSpan.FromSeconds(1))
.ObserveOn(Deployment.Current.Dispatcher)
.Subscribe(evt =>
{
if (txtCityName.Text.Length > 0)
{
string city = txtCityName.Text;
string url = $"http://www.webservicex.net/globalweather.asmx/GetWeather?CityName={city}&CountryName=USA";
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
if (e.Error == null)
{
string weatherData = e.Result;
// 这里可以对 weatherData 进行解析,提取有用的天气信息
lblWeatherInfo.Text = weatherData;
}
else
{
lblWeatherInfo.Text = "Error retrieving weather data.";
}
};
client.DownloadStringAsync(new Uri(url));
}
});
上述代码实现了以下功能:
1. 创建一个可观察数据源
cityInput
,包含
txtCityName
文本框的所有
KeyUp
事件。
2. 使用
Throttle
方法确保只有在用户输入间隔超过 1 秒时才会触发后续操作。
3. 使用
ObserveOn
方法确保操作在 UI 线程上执行,避免跨线程访问问题。
4. 当用户输入城市名称后,构造一个包含城市名称的 URL,使用
WebClient
异步下载天气数据。
5. 当下载完成后,根据结果更新
lblWeatherInfo
文本块的内容。
8. 错误处理与恢复
在实际应用中,可能会遇到各种错误,如网络连接问题、服务器响应错误等。为了提高应用程序的健壮性,我们需要进行错误处理和恢复。
在上述代码中,我们已经对
WebClient
的
DownloadStringCompleted
事件进行了简单的错误处理。当下载过程中出现错误时,会在界面上显示错误信息。
以下是一个更完善的错误处理示例:
cityInput.Throttle(TimeSpan.FromSeconds(1))
.ObserveOn(Deployment.Current.Dispatcher)
.Subscribe(evt =>
{
if (txtCityName.Text.Length > 0)
{
string city = txtCityName.Text;
string url = $"http://www.webservicex.net/globalweather.asmx/GetWeather?CityName={city}&CountryName=USA";
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
if (e.Error == null)
{
string weatherData = e.Result;
try
{
// 解析天气数据
// 这里可以添加更复杂的解析逻辑
lblWeatherInfo.Text = weatherData;
}
catch (Exception ex)
{
lblWeatherInfo.Text = "Error parsing weather data: " + ex.Message;
}
}
else
{
if (e.Error is WebException webEx)
{
switch (webEx.Status)
{
case WebExceptionStatus.NameResolutionFailure:
lblWeatherInfo.Text = "Network error: Name resolution failed.";
break;
case WebExceptionStatus.ConnectFailure:
lblWeatherInfo.Text = "Network error: Unable to connect to the server.";
break;
case WebExceptionStatus.ProtocolError:
lblWeatherInfo.Text = "Server error: " + ((HttpWebResponse)webEx.Response).StatusCode;
break;
default:
lblWeatherInfo.Text = "Network error: " + webEx.Status;
break;
}
}
else
{
lblWeatherInfo.Text = "Error retrieving weather data: " + e.Error.Message;
}
}
};
client.DownloadStringAsync(new Uri(url));
}
});
在这个示例中,我们不仅处理了下载过程中的错误,还对解析天气数据时可能出现的异常进行了捕获和处理。同时,针对不同类型的网络错误,给出了更详细的错误信息。
9. 总结与展望
通过前面的学习和实践,我们已经掌握了使用 Rx.NET 进行事件处理、异步编程、优化搜索体验、添加动画、遵循设计准则以及处理错误恢复等知识。从构建 Flickr 图片搜索应用程序到天气应用程序,我们逐步深入了解了 Rx.NET 的强大功能。
以下是使用 Rx.NET 开发应用程序的一些优点总结:
| 优点 | 说明 |
| — | — |
| 简化异步编程 | Rx.NET 抽象了线程和并发的许多问题,使异步编程变得更加简单和直观。 |
| 统一事件处理 | 可以将任何事件视为可观察数据源,使用统一的方式处理各种事件。 |
| 提高代码可读性和可维护性 | 通过声明式的编程方式,使代码更加简洁和易于理解。 |
| 支持错误处理和恢复 | 可以方便地处理各种异常情况,提高应用程序的健壮性。 |
展望未来,Rx.NET 还有很多其他的应用场景和功能等待我们去探索。例如,可以结合其他技术,如传感器数据处理、实时数据分析等,开发出更加复杂和强大的应用程序。同时,随着技术的不断发展,Rx.NET 也会不断更新和完善,为开发者提供更多的便利和可能性。
下面是构建天气应用程序的流程图:
graph LR
A[创建项目] --> B[添加引用]
B --> C[设计界面]
C --> D[实现逻辑]
D --> E[错误处理与恢复]
通过这个流程图,我们可以清晰地看到构建天气应用程序的主要步骤。从项目创建开始,逐步添加引用、设计界面、实现逻辑,并进行错误处理和恢复,最终完成一个功能完善、健壮的天气应用程序。在未来的开发中,我们可以根据这个流程,结合具体需求,开发出更多优秀的应用程序。
超级会员免费看
863

被折叠的 条评论
为什么被折叠?



