閱讀677 返回首頁    go 技術社區[雲棲]


《C#並發編程經典實例》—— 超時

聲明:本文是《C#並發編程經典實例》的樣章,感謝圖靈授權並發編程網站發布樣章,禁止以任何形式轉載此文。

問題
我們希望事件能在預定的時間內到達,即使事件不到達,也要確保程序能及時進行響應。
通常此類事件是單一的異步操作(例如,等待 Web 服務請求的響應)。

解決方案
Timeout 操 作 符 在 輸 入 流 上 建 立 一 個 可 調 節 的 超 時 窗 口。 一 旦 新 的 事 件 到 達, 就 重 置 超 時 窗 口。 如 果 超 過 期 限 後 事 件 仍 沒 到 達,Timeout 操 作 符 就 結 束 流, 並 產 生 一 個 包 含 TimeoutException 的 OnError 通知。

下麵的代碼向一個域名發出 Web 請求,並使用 1 秒作為超時值:

private void Button_Click(object sender, RoutedEventArgs e)
{
var client = new HttpClient();
client.GetStringAsync("https://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 操作符對優秀的程序來說是十分必要的,因為我們總是希望程序能及時響應,即 使外部環境不理想。它可用於任何事件流,尤其是在異步操作時。需要注意,此時內部的 操作並沒有真正取消,操作將繼續執行,直到成功或失敗。

最後更新:2017-05-23 11:02:28

  上一篇:go  深入理解Java內存模型(二)——重排序
  下一篇:go  深入理解Java內存模型(三)——順序一致性