1. 程式人生 > >《C#併發程式設計經典例項》—— 超時

《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 操作符對優秀的程式來說是十分必要的,因為我們總是希望程式能及時響應,即 使外部環境不理想。它可用於任何事件流,尤其是在非同步操作時。需要注意,此時內部的 操作並沒有真正取消,操作將繼續執行,直到成功或失敗。