#(68):訪問網絡(4)
前面幾章我們了解了如何使用`QNetworkAccessManager`?訪問網絡。在此基礎上,我們已經實現了一個簡單的查看天氣的程序。在這個程序中,我們使用`QNetworkAccessManager`進行網絡的訪問,從一個網絡 API 獲取某個城市的當前天氣狀況。
如果你仔細觀察就會發現,即便我們沒有添加任何相關代碼,`QNetworkAccessManager`的網絡訪問并不會阻塞 GUI 界面。也就是說,即便是在進行網絡訪問的時候,我們的界面還是可以響應的。相比之下,如果你對 Java 熟悉,就會了解到,在 Java 中,進行 Socket 通訊時,界面默認是阻塞的,當程序進行網絡訪問操作時,界面不能對我們的操作做出任何響應。由此可以看出,`QNetworkAccessManager`的網絡訪問默認就是異步的、非阻塞的。這樣的實現固然很好,也符合大多數程序的應用情形:我們當然希望程序界面能夠始終對用戶操作做出響應。不過,在某些情況下,我們還是希望會有一些同步的網絡操作。典型的是登錄操作。在登錄時,我們必須要等待網絡返回結果,才能讓界面做出響應:是驗證成功進入系統,還是驗證失敗做出提示?這就是本章的主要內容:如何使用`QNetworkAccessManager`進行同步網絡訪問。
當我們重新運行先前編譯好的程序,可以看看這樣一個操作:由于我們的界面是不阻塞的,那么當我們第一次點擊了 Refresh 按鈕之后,馬上切換城市再點擊一次 Refresh 按鈕,就會看到第一次的返回結果一閃而過。這是因為第一次網絡請求尚未完成時,用戶又發送了一次請求,Qt 會將兩次請求的返回結果順序顯示。這樣處理結果可能會出現與預期不一致的情況(比如第一次請求響應由于某種原因異常緩慢,第二次卻很快,此時第二次結果會比第一次先到,那么很明顯,當第一次結果返回時,第二次的結果就會被覆蓋掉。我們假設認為用戶需要第二次的返回,那么就會出現異常)。
要解決這種情況,我們可以在有網絡請求時將界面鎖死,不允許用戶進行更多的操作(更好的方法是僅僅鎖住某些按鈕,而不是整個界面。不過這里我們以鎖住整個界面為例)。我們的解決方案很簡單:當`QNetworkAccessManager`發出請求之后,我們進入一個新的事件循環,將操作進行阻塞。我們的代碼示例如下:
~~~
void fetchWeather(const QString &cityName)
{
QEventLoop eventLoop;
connect(netWorker, &NetWorker::finished,
&eventLoop, &QEventLoop::quit);
QNetworkReply *reply = netWorker->get(QString("http://api.openweathermap.org/data/2.5/weather?q=%1&mode=json&units=metric&lang=zh_cn").arg(cityName));
replyMap.insert(reply, FetchWeatherInfo);
eventLoop.exec();
}
~~~
注意,我們在函數中創建了一個`QEventLoop`實例,將其`quit()`與`NetWorker::finished()`信號連接起來。當`NetWorker::finished()`信號發出時,`QEventLoop::quit()`就會被調用。在`NetWorker::get()`執行之后,調用`QEventLoop::exec()`函數開始事件循環。此時界面就是被阻塞。
現在我們只是提供了一種很簡單的思路。當然這并不是最好的思路:程序界面直接被阻塞,用戶獲得不了任何提示,會誤以為程序死掉。更好的做法是做一個恰當的提示,不過這已經超出我們本章的范疇。更重要的是,這種思路并不完美。**如果你的程序是控制臺程序(沒有 GUI 界面),或者是某些特殊的情況下,會造出死鎖!**控制臺程序中發送死鎖的原因在于在非 GUI 程序中另外啟動事件循環會將主線程阻塞,`QNetworkAccessManager`的所有信號都不會收到。“某些特殊的情況”,我們會在后面有關線程的章節詳細解釋。不過,要完美解決這個問題,我們必須使用另外的線程。[這里](http://www.codeproject.com/Articles/484905/Use-QNetworkAccessManager-for-synchronous-download)有一個通用的解決方案,感興趣的童鞋可以詳細了解下。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)