声明:本文是《C#并发编程经典实例》的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文。
问题
我们希望事件能在预定的时间内到达,即使事件不到达,也要确保程序能及时进行响应。
通常此类事件是单一的异步操作(例如,等待 Web 服务请求的响应)。
解决方案
Timeout 操 作 符 在 输 入 流 上 建 立 一 个 可 调 节 的 超 时 窗 口。 一 旦 新 的 事 件 到 达, 就 重 置 超 时 窗 口。 如 果 超 过 期 限 后 事 件 仍 没 到 达,Timeout 操 作 符 就 结 束 流, 并 产 生 一 个 包 含 TimeoutException 的 OnError 通知。
下面的代码向一个域名发出 Web 请求,并使用 1 秒作为超时值:
private void Button_Click(object sender, RoutedEventArgs e) { var client = new HttpClient(); client.GetStringAsync("http://www.example.com/").ToObservable() .Timeout(TimeSpan.FromSeconds(1)) .Subscribe( x => Trace.WriteLine(DateTime.Now.Second + ": Saw " + x.Length), ex => Trace.WriteLine(ex)); }
Timeout 非常适用于异步操作,例如 Web 请求,但它也能用于任何事件流。下面的例子在
监视鼠标移动时使用 Timeout,使用起来更加简单:
private void Button_Click(object sender, RoutedEventArgs e) { Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>( handler => (s, a) => handler(s, a), handler => MouseMove += handler, handler => MouseMove -= handler) .Select(x => x.EventArgs.GetPosition(this)) .Timeout(TimeSpan.FromSeconds(1)) .Subscribe( x => Trace.WriteLine(DateTime.Now.Second + ": Saw " + (x.X + x.Y)), ex => Trace.WriteLine(ex)); }
我移动了一下鼠标,然后停止 1 秒,得到如下结果:
16: Saw 180
16: Saw 178
16: Saw 177
16: Saw 176
System.TimeoutException: The operation has timed out.
值得注意的是,一旦向 OnError 发送 TimeoutException,整个事件流就结束了,不会继续 传来鼠标移动事件。为了阻止这种情况出现,Timeout 操作符具有重载方式,在超时发生 时用另一个流来替代,而不是抛出异常并结束流。
下面的例子,在超时之前观察鼠标移动,超时发生后进行切换,观察鼠标点击:
private void Button_Click(object sender, RoutedEventArgs e)
{
var clicks = Observable.FromEventPattern
<MouseButtonEventHandler, MouseButtonEventArgs>(
handler => (s, a) => handler(s, a), handler => MouseDown += handler, handler => MouseDown -= handler)
.Select(x => x.EventArgs.GetPosition(this));
Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
handler => (s, a) => handler(s, a), handler => MouseMove += handler, handler => MouseMove -= handler)
.Select(x => x.EventArgs.GetPosition(this))
.Timeout(TimeSpan.FromSeconds(1), clicks)
.Subscribe(
x => Trace.WriteLine(
DateTime.Now.Second + “: Saw ” + x.X + “,” + x.Y), ex => Trace.WriteLine(ex));
}
我先移动一下鼠标,停止 1 秒,然后在两个不同的位置点击。下面的输出表明,超时发生
前鼠标移动事件在进行快速移动,超时后变成两个鼠标点击事件:
49: Saw 95,39
49: Saw 94,39
49: Saw 94,38
49: Saw 94,37
53: Saw 130,141
55: Saw 469,4
讨论
Timeout 操作符对优秀的程序来说是十分必要的,因为我们总是希望程序能及时响应,即 使外部环境不理想。它可用于任何事件流,尤其是在异步操作时。需要注意,此时内部的 操作并没有真正取消,操作将继续执行,直到成功或失败。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/120507.html