MVC無人不知,可不少程序員對MVC的概念的理解彷佛有誤,換言之他們一直在錯用MVC,儘管即便如此軟件也能被寫出來,然而軟件內部代碼的組織方式倒是不科學的,這會影響到軟件的可維護性、可移植性,代碼的可重用性。html
MVC即Model、View、Controller即模型、視圖、控制器。我在和同行討論技術,閱讀別人的代碼時發現,不少程序員傾向於將軟件的業務邏輯放在Controller裏,將數據庫訪問操做的代碼放在Model裏。程序員
最終軟件(網站)的代碼結構是,View層是界面,Controller層是業務邏輯,Model層是數據庫訪問。web
不知道你們知不知道另一種軟件開發模式三層架構,它和MVC類似之處是也分爲三層,分別是UI層表示用戶界面,BLL層表示業務邏輯,DAL層表示數據訪問。三層架構曾經紅極一時,MVC大行其道以後它就銷聲匿跡了, 可如今看來, 它彷佛只是改頭換面, 裝扮成MVC的樣子,而且深受程序員們的歡迎,由於它的這種分層方式和前文描述的MVC一模一樣。面試
再說的直白點,不少程序員將MVC當成了三層架構在用,這看起來彷佛沒什麼問題,畢竟三層架構也是一種和MVC齊名的架構模式。可問題在於用三成架構的思路寫MVC,那麼寫出來的東西既不是三成架構也不是MVC,到是像一個什麼都不是四不像。熟悉天龍八部的同窗應該知道這樣一段情節,吐蕃番僧鳩摩智強行用道家的小無相功爲基礎修煉少林的七十二絕技和易筋經最終致使走火入魔。其實用這個例子來形容如今一些程序員用三層架構的思想寫MVC最恰當不過了,三層架構的核心思想是面向接口編程和各層之間的解耦和可替換性,MVC框架中沒有這種概念,由於MVC要面對的問題本就不是三成架構要面對的問題,因此以MVC爲基礎寫出來的三成架構是不會具有三層架構的核心要義的,換言之,這種代碼是放棄了三層架構和MVC的精華,得到了它們的糟粕,是愚蠢的編碼方式。redis
我吐槽了這麼多, 對於吐槽的理由要是說不出個因此然來,估計要被人噴死,下面就來講說MVC本質原理和正確使用方式,固然,這裏的MVC指的最純粹MVC,適合各種軟件,而不只僅指Web框架中的變體MVC,然而萬變不離其宗,文中所述的MVC思想一樣適用於Web開發。數據庫
MVC要實現的目標是將軟件用戶界面和業務邏輯分離以使代碼可擴展性、可複用性、可維護性、靈活性增強。編程
View層是界面,Model層是業務邏輯,Controller層用來調度View層和Model層,將用戶界面和業務邏輯合理的組織在一塊兒,起粘合劑的效果。因此Controller中的內容能少則少,這樣才能提供最大的靈活性。設計模式
比方說,有一個View會提交數據給Model進行處理以實現具體的行爲,View一般不會直接提交數據給Model,它會先把數據提交給Controller,而後Controller再將數據轉發給Model。假如此時程序業務邏輯的處理方式有變化,那麼只須要在Controller中將原來的Model換成新實現的Model就能夠了,控制器的做用就是這麼簡單, 用來將不一樣的View和不一樣的Model組織在一塊兒,順便替雙方傳遞消息,僅此而已。瀏覽器
合理的使用MVC有不少好處,要一一道滿是一件異常困難的任務。在這裏咱們經過一個反面示例來側面的證實正確使用MVC的好處與依據。緩存
如前文所言, 不少程序員偏心於將業務邏輯放在Controller中,我極力反對這種作法,如今我就來證實這中作法的錯誤性。
咱們知道在寫程序時,業務邏輯的重複使用是常常要面對的場景。 若是業務邏輯寫在控制器中, 要重用它的惟一方法就是將它提高到父類之中,經過繼承來達到代碼複用的效果。但這麼作會帶來一個巨大的反作用,違背了一項重要的面向對象設計原則:接口隔離。
什麼是接口隔離,我在這裏簡單的講述一下。通俗一點講,接口隔離就是當一個類須要繼承另外一個類時, 若是被繼承的類中有繼承的類用不到的方法或者屬性時,就不要去實現這個繼承。若是真的情非得已必需要繼承,那麼也須要從被繼承的類中再提取出一個只包含須要部分功能的新類型,最終去繼承這個新類型纔是正確的作法。 換句話說,實現繼承的時候,不要去繼承那些用不到的事物。
回到以前的話題,經過繼承父控制器的方式複用業務邏輯時,每每會出現爲了重用一個方法而繼承來一大堆用不到的方法,表面上看起來彷佛沒什麼問題,可是這會使代碼變的難以理解,
久而久之, 軟件的代碼會朝着不健康的方向發展。
要知道,使用繼承的條件是很苛刻的,咱們學習面向對象變編程繼承特性時,第一課就是隻有知足IS-A(是一個)關係時纔可使用繼承,若是僅僅是複用代碼,並非咱們使用繼承的理由。使用組合是複用代碼提倡的方式,也就是所謂的HAS-A(有一個)的關係,相信每一個程序員都聽過「少用繼承,多有組合」這句話,這句話是軟件開發業的先驅們千錘百煉總結出來的,值得咱們去遵循。
各Model之間是能夠相互調用的, Controller也能夠無障礙的調用Model,所以將業務邏輯放在Model中能夠靈活的使用組合的方式複用代碼。
而Controller之間是不能夠相互調用的,要複用代碼只能將代碼提高至父類,經過繼承實現,顯然這種作法既不正確,也不靈活,所以徹底不提倡。
綜上所述,僅僅只是代碼複用這一點,也足以將「厚Controller,薄Model」這種不健康的MVC思想打入十八層地獄。
如今咱們大概知道了代碼應該如何分佈於MVC三層之間, 知其然,而且也知其因此然。接下來咱們再從另外一個角度深入剖析MVC,脫它個精光,讓它赤條條展現在咱們眼前。
衆所周知,GoF總結過23個設計模式,這23個設計模式是某些特定的編程問題的特效藥,這是業內公認的。
MVC是一種模式,但卻在GoF總結出來的這個23個設計模式以外,確切的說它不是一種設計模式,它是多種設計模式的組合,並不只僅只是一個單獨的一個模式。
組成MVC的三個模式分別是組合模式、策咯模式、觀察者模式,MVC在軟件開發中發揮的威力,最終離不開這三個模式的默契配合。 那些崇尚設計模式無用論的程序員,請了解只要大家使用MVC,就離不開設計模式。
注意,如下內容以這三個設計模式的知識爲基礎,若是對這三個設計模式沒概念,或許會閱讀困難。
先說組合模式在MVC中扮演什麼樣的角色。
組合模式只在視圖層活動, 視圖層的實現用的就是組合模式,固然,這裏指的實現是底層的實現,是由編程框架廠商作的事情,用不着普通程序員插手。
組合模式的類層次結構是樹狀的, 而咱們作Web時視圖層是html頁面,html的結構不正是樹狀的嗎,這其實就是一個組合模式的應用,只是瀏覽器廠商已經把界面相關的工做幫咱們作掉了,但它確確實實是咱們應用MVC的其中一部分,只是咱們感受不到罷了,這也是咱們以爲View是實現起來最簡單最沒有歧義的一層的緣由。
除網頁之外的其餘用戶界面程序,如WPF、Android、Asp.net等等都是使用樹狀結構來組織界面控件對象的,由於組合模式就是從界面設計的通用解決方案總提煉出來的。因此與其說MVC選擇了組合模式,還不如說組合模式是一定會存在MVC中的,由於只要涉及到用戶界面,組合模式就一定存。事實上即便不理解組合模式,也不影響程序員正確的使用MVC,組合模式本就存在於程序員接觸不到的位置。
然而,觀察者模式和策略模式就顯得比較重要,是實實在在MVC中接觸的到的部分。
觀察者模式有兩部分組成,被觀察的對象和觀察者,觀察者也被稱爲監聽者。對應到MVC中,Model是被觀察的對象,View是觀察者,Model層一旦發生變化,View層即被通知更新。View層和Model層互相之間是持有引用的。 咱們在開發Web MVC程序時,由於視圖層的html和Model層的業務邏輯之間隔了一個http,因此不能顯示的進行關聯,可是他們觀察者和收聽者的關係卻沒有改變。當View經過http提交數據給服務器,服務器上的Model接受到數據執行某些操做,再經過http響應將結果回送給View,View(瀏覽器)接受到數據更新界面,這不正是一個接受到通知並執行更新的行爲嗎,是觀察者模式的另外一種表現形式。
可是,脫離Web,當經過代碼去純粹的表示一個MVC結構的時候,View和Model間無疑是觀察者和被觀察的關係,是以觀察者模式爲理論基礎的。即便在Web中由於http壁壘的緣由致使真正的實現有點走樣,可是原理核心和思路哲學倒是不變的。
最後是策略模式。策略模式是View和Controller之間的關係,Controller是View的一個策略,Controller對於View是可替換的, View和Controller的關係是一對多,在實際的開發場景中,也常常會碰到一個View被多個Controller引用,這即便策咯模式的一種體現,只是不那麼直觀而已。
總結一下,關於MVC各層之間關係所對應的設計模式
View層,單獨實現了組合模式
Model層和View層,實現了觀察者模式
View層和Controller層,實現了策咯模式
MVC就是將這三個設計模式在一塊兒用了,將這三個設計模式弄明白,MVC將毫無神祕感可言。若是不瞭解這三個設計模式去學習MVC,那無論怎麼學總歸是隻知其一;不知其二,用的時候也不免不會出想問題。
再次回到最前面討論的業務邏輯應該放在Controller仍是Model的問題上,從設計模式的角度講,策略模式中的策略一般都很小很薄,不會包含太多的內容, Controller是一個策略, 天然不該該在裏面放置過多的內容,不然要替換一個新的會至關麻煩,與此同時也會破壞View-Model的觀察者模式,彷彿View-Controller之間即實現了策略模式又實現了觀察者模式,這種混亂是罪惡的根源,是製造焦油坑讓程序員陷入其中沒法自拔的罪魁禍首。切忌,應當避免。
注:此文核心思想來自《head first設計模式》
C#+HtmlAgilityPack+Dapper走一波爬蟲
最近由於公司業務須要,又有機會擼winform了,此次的需求是由於公司有項目申報的這塊業務,項目申報前期須要關注政府發佈的相關動態信息,政府部門網站過多,人工須要一個一個網站去瀏覽和查閱,有時候還會遺漏掉,所以呢,咱們打算用爬蟲+移動端web來作,我呢主要負責爬蟲和web Api。
爬蟲篇
爬蟲主要採用.Net強大的開源解析HTML元素的類庫HtmlAgilityPack,操做過XML的童鞋應該很快就能夠上手,經過分析XPath來解析HTML,很是的方便的,不過還有一款不錯的叫Jumony,沒用過,對HtmlAgilityPack比較熟悉,因此首選了HtmlAgilityPack來做爲主力軍。
HtmlAgilityPack的基本使用能夠參考這篇 https://www.cnblogs.com/GmrBrian/p/6201237.html,
效果圖,多圖慎入
採集廣西財政廳例子
由於是政府發佈的出來的信息,因此信息的對外開放的,只是機器代替人工來瀏覽,不會被和諧的,主要採集文章列表和文章內容,以廣西財政廳網站爲例子。
First
加載網站這個就不用說了,先查看網站的字符編碼,如圖<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> ,而後設置HtmlAgilityPack中的OverrideEncoding屬性,
htmlAgilityPack.OverrideEncoding = Encoding.UTF8;
Second
分析文章列表,瀏覽器F12查看HTML標籤狀況,能夠分析出XPath爲
//ul[@class='dzjzw_list_main_ul']//li
流程代碼:
//獲取第一頁的內容 HtmlNode row = GetHtmlDoc(htmlWeb, url); //根據xpath獲取列表 var list = row.SelectNodes("//ul[@class='dzjzw_list_main_ul']//li"); foreach (var data in list) { .... } /// <summary> /// 這裏偶爾會瀏覽網頁失敗的,因此失敗了多瀏覽幾回 /// </summary public static HtmlNode GetHtmlDoc(HtmlWeb htmlWeb, string url) { try { var doc = GetDoc(htmlWeb, url); if (doc == null) { int againIdx = 0; while (againIdx++ < 5) { System.Threading.Thread.Sleep(1000); doc = GetDoc(htmlWeb, url); if (doc != null) break; } if (doc == null) { var htmlData = HttpHelper.Get<string>(url).Result;//.GetStringAsync(url).Result; return HtmlNode.CreateNode(htmlData); } else { return doc.DocumentNode; } } return doc.DocumentNode; } catch { Log.Error("未能正確訪問地址:" + url); return null; } }
文章內容的連接的XPath標籤
//a
文章發佈的時間XPath標籤
//span[@class='date']
均可以使用 HtmlNode.InnerText 來獲取到相關值,很是的方便。
Third
文章詳細內容也如此,經過分析XPath來分析便可,最頭疼的是翻頁的問題,由於政府網站使用的技術通常都是比較那個的,你懂的,在這裏的翻頁也比較簡單,經過拼接URL來進行翻頁便可,有些使用到oncilck來觸發的,有些表單提交,要具體問題具體分析了,用Fiddler和瀏覽器的F12大法來分析翻頁數據來源,因此這裏的例子比較簡單
Fourth
爬取到的以後,再來一個釘釘通知,在羣里拉入一個機器人,能夠參考釘釘的開發文檔(https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.ece6g3&treeId=257&articleId=105735&docType=1#)
這樣咱們爬取的消息就第一時間通知到羣裏的小夥伴啦,是否是很炫酷,哈哈哈。
後面作完了再上傳到GitHub吧,下班下班。
StackExchange.Redis 二次封裝
在NuGet直接搜索StackExchange.Redis,下載引用包;
幫助類:
public class RedisUtils { /// <summary> /// redis配置文件信息 /// </summary> public static string RedisPath = "172.16.3.82:6379"; private static object _locker = new Object(); private static ConnectionMultiplexer _instance = null; /// <summary> /// 使用一個靜態屬性來返回已鏈接的實例,以下列中所示。這樣,一旦 ConnectionMultiplexer 斷開鏈接,即可以初始化新的鏈接實例。 /// </summary> public static ConnectionMultiplexer Instance { get { if (_instance == null) { lock (_locker) { if (_instance == null || !_instance.IsConnected) { _instance = ConnectionMultiplexer.Connect(RedisPath); //註冊以下事件 _instance.ConnectionFailed += MuxerConnectionFailed; _instance.ConnectionRestored += MuxerConnectionRestored; _instance.ErrorMessage += MuxerErrorMessage; _instance.HashSlotMoved += MuxerHashSlotMoved; _instance.InternalError += MuxerInternalError; } } } return _instance; } } #region Keys /// <summary> /// 判斷鍵是否存在 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵值</param> /// <returns></returns> public static bool KeyExists(int dbIndex, string key) { var db = Instance.GetDatabase(dbIndex); return db.KeyExists(key); } /// <summary> /// 爲指定的鍵設置失效時間 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <param name="expiry">時間間隔</param> /// <returns></returns> public static bool SetExpire(int dbIndex, string key, TimeSpan? expiry) { var db = Instance.GetDatabase(dbIndex); return db.KeyExpire(key, expiry); } /// <summary> /// 爲指定的鍵設置失效時間 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <param name="timeout">時間間隔(秒)</param> /// <returns></returns> public static bool SetExpire(int dbIndex, string key, int timeout = 0) { var db = Instance.GetDatabase(dbIndex); return db.KeyExpire(key, DateTime.Now.AddSeconds(timeout)); } /// <summary> /// 刪除鍵 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <returns></returns> public static bool KeyDelete(int dbIndex, string key) { var db = Instance.GetDatabase(dbIndex); return db.KeyDelete(key); } /// <summary> /// 鍵重命名 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="oldKey">舊值</param> /// <param name="newKey">新值</param> /// <returns></returns> public static bool KeyRename(int dbIndex, string oldKey, string newKey) { var db = Instance.GetDatabase(dbIndex); return db.KeyRename(oldKey, newKey); } #endregion #region Strings /// <summary> /// 獲取字符串數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key">Redis鍵</param> /// <returns></returns> public static string StringGet(int dbIndex, string key) { var db = Instance.GetDatabase(dbIndex); if (db != null) { return db.StringGet(key); } return string.Empty; } /// <summary> /// 獲取對象類型數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key">Redis鍵</param> /// <returns></returns> public static T StringGet<T>(int dbIndex, string key) where T : class { T data = default(T); var db = Instance.GetDatabase(dbIndex); if (db != null) { var value = db.StringGet(key); if (string.IsNullOrWhiteSpace(value)) return data; return JsonConvert.DeserializeObject<T>(value); } return data; } /// <summary> /// 設置值類型的值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <param name="value">值</param> public static bool StringSet(int dbIndex, string key, RedisValue value, TimeSpan? expiry) { var db = Instance.GetDatabase(dbIndex); return db.StringSet(key, value, expiry); } /// <summary> /// 設置對象類型的值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <param name="value">值</param> public static bool StringSet<T>(int dbIndex, string key, T value, TimeSpan? expiry) where T : class { if (value == default(T)) { return false; } var db = Instance.GetDatabase(dbIndex); return db.StringSet(key, JsonConvert.SerializeObject(value), expiry); } #endregion #region Hashes /// <summary> /// Hash是否存在 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">HashId</param> /// <param name="key">鍵</param> /// <returns></returns> public static bool HashExists(int dbIndex, string hashId, string key) { var db = Instance.GetDatabase(dbIndex); return db.HashExists(hashId, key); } /// <summary> /// Hash長度 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">HashId</param> /// <returns></returns> public static long HashLength(int dbIndex, string hashId) { var db = Instance.GetDatabase(dbIndex); var length = db.HashLength(hashId); return length; } /// <summary> /// 設置哈希值 /// </summary> /// <typeparam name="T">哈希值類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">哈希ID</param> /// <param name="key">鍵</param> /// <param name="t">哈希值</param> /// <returns></returns> public static bool HashSet<T>(int dbIndex, string hashId, string key, T t) { var db = Instance.GetDatabase(dbIndex); var value = JsonConvert.SerializeObject(t); return db.HashSet(hashId, key, value); } /// <summary> /// 獲取哈希值 /// </summary> /// <typeparam name="T">哈希值類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">哈希ID</param> /// <param name="key">鍵</param> /// <returns></returns> public static T HashGet<T>(int dbIndex, string hashId, string key) { var db = Instance.GetDatabase(dbIndex); string value = db.HashGet(hashId, key); if (string.IsNullOrWhiteSpace(value)) return default(T); return JsonConvert.DeserializeObject<T>(value); } /// <summary> /// 獲取哈希值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">哈希ID</param> /// <param name="key">鍵</param> /// <returns></returns> public static string HashGet(int dbIndex, string hashId, string key) { var db = Instance.GetDatabase(dbIndex); string value = db.HashGet(hashId, key).ToString(); return value; } /// <summary> /// 獲取哈希值的全部鍵 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">哈希ID</param> /// <returns></returns> public static List<string> HashKeys(int dbIndex, string hashId) { var db = Instance.GetDatabase(dbIndex); var result = new List<string>(); var list = db.HashKeys(hashId).ToList(); if (list.Count > 0) { list.ForEach(x => { var value = JsonConvert.DeserializeObject<string>(x); result.Add(value); }); } return result; } /// <summary> /// 獲取全部哈希值 /// </summary> /// <typeparam name="T">哈希值類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">哈希ID</param> /// <returns></returns> public static List<T> HashGetAll<T>(int dbIndex, string hashId) { var db = Instance.GetDatabase(dbIndex); var result = new List<T>(); var list = db.HashGetAll(hashId).ToList(); if (list.Count > 0) { list.ForEach(x => { var value = JsonConvert.DeserializeObject<T>(x.Value); result.Add(value); }); } return result; } /// <summary> /// 刪除哈希值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="hashId">哈希ID</param> /// <param name="key">鍵</param> /// <returns></returns> public static bool HashDelete(int dbIndex, string hashId, string key) { var db = Instance.GetDatabase(dbIndex); return db.HashDelete(hashId, key); } #endregion #region Lists /// <summary> /// 集合長度 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="listId">集合ID</param> /// <returns></returns> public static long ListLength(int dbIndex, string listId) { var db = Instance.GetDatabase(dbIndex); return db.ListLength(listId); } /// <summary> /// 向集合中添加元素 /// </summary> /// <typeparam name="T">元素類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="listId">集合ID</param> /// <param name="list">元素值</param> /// <returns></returns> public static long AddList<T>(int dbIndex, string listId, List<T> list) { var db = Instance.GetDatabase(dbIndex); if (list != null && list.Count > 0) { foreach (var item in list) { db.ListRightPush(listId, JsonConvert.SerializeObject(item)); } } return ListLength(dbIndex, listId); } /// <summary> /// 獲取集合元素(默認獲取整個集合) /// </summary> /// <typeparam name="T">元素類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="listId">集合ID</param> /// <param name="start">起始位置(0表示第1個位置)</param> /// <param name="stop">結束位置(-1表示倒數第1個位置)</param> /// <returns></returns> public static List<T> GetList<T>(int dbIndex, string listId, long start = 0, long stop = -1) { var db = Instance.GetDatabase(dbIndex); var result = new List<T>(); var list = db.ListRange(listId, start, stop).ToList(); if (list.Count > 0) { list.ForEach(x => { var value = JsonConvert.DeserializeObject<T>(x); result.Add(value); }); } return result; } #endregion #region ZSet #region 同步方法 /// <summary> /// 添加一個值到Key /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="score">排序分數,爲空將獲取集合中最大score加1</param> /// <returns></returns> public static bool SortedSetAdd<T>(int dbIndex, string key, T value, double? score = null) { var db = Instance.GetDatabase(dbIndex); double scoreNum = score ?? _GetScore(key, db); return db.SortedSetAdd(key, ConvertJson<T>(value), scoreNum); } /// <summary> /// 添加一個集合到Key /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="score">排序分數,爲空將獲取集合中最大score加1</param> /// <returns></returns> public static long SortedSetAdd<T>(int dbIndex, string key, List<T> value, double? score = null) { var db = Instance.GetDatabase(dbIndex); double scoreNum = score ?? _GetScore(key, db); SortedSetEntry[] rValue = value.Select(o => new SortedSetEntry(ConvertJson<T>(o), scoreNum++)).ToArray(); return db.SortedSetAdd(key, rValue); } /// <summary> /// 獲取集合中的數量 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <returns></returns> public static long SortedSetLength(int dbIndex,string key) { var db = Instance.GetDatabase(dbIndex); return db.SortedSetLength(key); } /// <summary> /// 獲取指定起始值到結束值的集合數量 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="startValue">起始值</param> /// <param name="endValue">結束值</param> /// <returns></returns> public static long SortedSetLengthByValue<T>(int dbIndex, string key, T startValue, T endValue) { var db = Instance.GetDatabase(dbIndex); var sValue = ConvertJson<T>(startValue); var eValue = ConvertJson<T>(endValue); return db.SortedSetLengthByValue(key, sValue, eValue); } /// <summary> /// 獲取指定Key的排序Score值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="value"></param> /// <returns></returns> public static double? SortedSetScore<T>(int dbIndex, string key, T value) { var db = Instance.GetDatabase(dbIndex); var rValue = ConvertJson<T>(value); return db.SortedSetScore(key, rValue); } /// <summary> /// 獲取指定Key中最小Score值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <returns></returns> public static double SortedSetMinScore(int dbIndex, string key) { var db = Instance.GetDatabase(dbIndex); double dValue = 0; var rValue = db.SortedSetRangeByRankWithScores(key, 0, 0, Order.Ascending).FirstOrDefault(); dValue = rValue != null ? rValue.Score : 0; return dValue; } /// <summary> /// 獲取指定Key中最大Score值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <returns></returns> public static double SortedSetMaxScore(int dbIndex, string key) { var db = Instance.GetDatabase(dbIndex); double dValue = 0; var rValue = db.SortedSetRangeByRankWithScores(key, 0, 0, Order.Descending).FirstOrDefault(); dValue = rValue != null ? rValue.Score : 0; return dValue; } /// <summary> /// 刪除Key中指定的值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="value"></param> public static long SortedSetRemove<T>(int dbIndex, string key, params T[] value) { var db = Instance.GetDatabase(dbIndex); var rValue = ConvertRedisValue<T>(value); return db.SortedSetRemove(key, rValue); } /// <summary> /// 刪除指定起始值到結束值的數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="startValue">起始值</param> /// <param name="endValue">結束值</param> /// <returns></returns> public static long SortedSetRemoveRangeByValue<T>(int dbIndex, string key, T startValue, T endValue) { var db = Instance.GetDatabase(dbIndex); var sValue = ConvertJson<T>(startValue); var eValue = ConvertJson<T>(endValue); return db.SortedSetRemoveRangeByValue(key, sValue, eValue); } /// <summary> /// 刪除 從 start 開始的 stop 條數據 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="start"></param> /// <param name="stop"></param> /// <returns></returns> public static long SortedSetRemoveRangeByRank(int dbIndex, string key, long start, long stop) { var db = Instance.GetDatabase(dbIndex); return db.SortedSetRemoveRangeByRank(key, start, stop); } /// <summary> /// 根據排序分數Score,刪除從 start 開始的 stop 條數據 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="start"></param> /// <param name="stop"></param> /// <returns></returns> public static long SortedSetRemoveRangeByScore(int dbIndex, string key, double start, double stop) { var db = Instance.GetDatabase(dbIndex); return db.SortedSetRemoveRangeByScore(key, start, stop); } /// <summary> /// 獲取從 start 開始的 stop 條數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="start">起始數</param> /// <param name="stop">-1表示到結束,0爲1條</param> /// <param name="desc">是否按降序排列</param> /// <returns></returns> public static List<T> SortedSetRangeByRank<T>(int dbIndex, string key, long start = 0, long stop = -1, bool desc = false) { var db = Instance.GetDatabase(dbIndex); Order orderBy = desc ? Order.Descending : Order.Ascending; var rValue = db.SortedSetRangeByRank(key, start, stop, orderBy); return ConvetList<T>(rValue); } #endregion #region 異步方法 /// <summary> /// 添加一個值到Key /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="score">排序分數,爲空將獲取集合中最大score加1</param> /// <returns></returns> public static async Task<bool> SortedSetAddAsync<T>(int dbIndex, string key, T value, double? score = null) { var db = Instance.GetDatabase(dbIndex); double scoreNum = score ?? _GetScore(key, db); return await db.SortedSetAddAsync(key, ConvertJson<T>(value), scoreNum); } /// <summary> /// 添加一個集合到Key /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="score">排序分數,爲空將獲取集合中最大score加1</param> /// <returns></returns> public static async Task<long> SortedSetAddAsync<T>(int dbIndex, string key, List<T> value, double? score = null) { var db = Instance.GetDatabase(dbIndex); double scoreNum = score ?? _GetScore(key, db); SortedSetEntry[] rValue = value.Select(o => new SortedSetEntry(ConvertJson<T>(o), scoreNum++)).ToArray(); return await db.SortedSetAddAsync(key, rValue); } /// <summary> /// 獲取集合中的數量 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <returns></returns> public static async Task<long> SortedSetLengthAsync(int dbIndex, string key) { var db = Instance.GetDatabase(dbIndex); return await db.SortedSetLengthAsync(key); } /// <summary> /// 獲取指定起始值到結束值的集合數量 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="startValue">起始值</param> /// <param name="endValue">結束值</param> /// <returns></returns> public static async Task<long> SortedSetLengthByValueAsync<T>(int dbIndex, string key, T startValue, T endValue) { var db = Instance.GetDatabase(dbIndex); var sValue = ConvertJson<T>(startValue); var eValue = ConvertJson<T>(endValue); return await db.SortedSetLengthByValueAsync(key, sValue, eValue); } /// <summary> /// 獲取指定Key中最大Score值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <returns></returns> public static async Task<double> SortedSetMaxScoreAsync(int dbIndex, string key) { var db = Instance.GetDatabase(dbIndex); double dValue = 0; var rValue = (await db.SortedSetRangeByRankWithScoresAsync(key, 0, 0, Order.Descending)).FirstOrDefault(); dValue = rValue != null ? rValue.Score : 0; return dValue; } /// <summary> /// 刪除Key中指定的值 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="value"></param> public static async Task<long> SortedSetRemoveAsync<T>(int dbIndex, string key, params T[] value) { var db = Instance.GetDatabase(dbIndex); var rValue = ConvertRedisValue<T>(value); return await db.SortedSetRemoveAsync(key, rValue); } /// <summary> /// 刪除指定起始值到結束值的數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="startValue">起始值</param> /// <param name="endValue">結束值</param> /// <returns></returns> public static async Task<long> SortedSetRemoveRangeByValueAsync<T>(int dbIndex, string key, T startValue, T endValue) { var db = Instance.GetDatabase(dbIndex); var sValue = ConvertJson<T>(startValue); var eValue = ConvertJson<T>(endValue); return await db.SortedSetRemoveRangeByValueAsync(key, sValue, eValue); } /// <summary> /// 刪除 從 start 開始的 stop 條數據 /// </summary> /// <param name="dbIndex">數據庫</param> /// <param name="key"></param> /// <param name="start"></param> /// <param name="stop"></param> /// <returns></returns> public static async Task<long> SortedSetRemoveRangeByRankAsync(int dbIndex, string key, long start, long stop) { var db = Instance.GetDatabase(dbIndex); return await db.SortedSetRemoveRangeByRankAsync(key, start, stop); } #endregion #region 內部輔助方法 /// <summary> /// 將對象轉換成string字符串 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static string ConvertJson<T>(T value) { string result = value is string ? value.ToString() : JsonConvert.SerializeObject(value, Formatting.None); return result; } /// <summary> /// 獲取指定Key中最大Score值, /// </summary> /// <param name="key">key名稱,注意要先添加上Key前綴</param> /// <returns></returns> private static double _GetScore(string key, IDatabase db) { double dValue = 0; var rValue = db.SortedSetRangeByRankWithScores(key, 0, 0, Order.Descending).FirstOrDefault(); dValue = rValue != null ? rValue.Score : 0; return dValue + 1; } /// <summary> /// 將值集合轉換成RedisValue集合 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="redisValues"></param> /// <returns></returns> private static RedisValue[] ConvertRedisValue<T>(params T[] redisValues) => redisValues.Select(o => (RedisValue)ConvertJson<T>(o)).ToArray(); /// <summary> /// 將值反系列化成對象集合 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="values"></param> /// <returns></returns> public static List<T> ConvetList<T>(RedisValue[] values) { List<T> result = new List<T>(); foreach (var item in values) { var model = ConvertObj<T>(item); result.Add(model); } return result; } /// <summary> /// 將值反系列化成對象 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static T ConvertObj<T>(RedisValue value) { return value.IsNullOrEmpty ? default(T) : JsonConvert.DeserializeObject<T>(value); } /// <summary> /// 獲取幾個集合的交叉並集合,並保存到一個新Key中 /// </summary> /// <param name="db"></param> /// <param name="operation">Union:並集 Intersect:交集 Difference:差集 詳見 <see cref="SetOperation"/></param> /// <param name="destination">保存的新Key名稱</param> /// <param name="keys">要操做的Key集合</param> /// <returns></returns> private static long _SortedSetCombineAndStore(IDatabase db, SetOperation operation, string destination, params string[] keys) { RedisKey[] keyList = ConvertRedisKeysAddSysCustomKey(keys); var rValue = db.SortedSetCombineAndStore(operation, destination, keyList); return rValue; } /// <summary> /// 將string類型的Key轉換成 <see cref="RedisKey"/> 型的Key,並添加前綴字符串 /// </summary> /// <param name="redisKeys"></param> /// <returns></returns> private static RedisKey[] ConvertRedisKeysAddSysCustomKey(params string[] redisKeys) => redisKeys.Select(redisKey => (RedisKey)redisKey).ToArray(); #endregion #endregion #region 看成消息代理中間件使用 通常使用更專業的消息隊列來處理這種業務場景 /// <summary> /// 看成消息代理中間件使用 /// 消息組建中,重要的概念即是生產者,消費者,消息中間件。 /// </summary> /// <param name="channel"></param> /// <param name="message"></param> /// <returns></returns> public static long Publish(string channel, string message) { ISubscriber sub = Instance.GetSubscriber(); //return sub.Publish("messages", "hello"); return sub.Publish(channel, message); } /// <summary> /// 在消費者端獲得該消息並輸出 /// </summary> /// <param name="channelFrom"></param> /// <returns></returns> public static void Subscribe(string channelFrom) { ISubscriber sub = Instance.GetSubscriber(); sub.Subscribe(channelFrom, (channel, message) => { Console.WriteLine((string)message); }); } #endregion #region EventHandler /// <summary> /// 鏈接失敗 , 若是從新鏈接成功你將不會收到這個通知 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void MuxerConnectionFailed(object sender, ConnectionFailedEventArgs e) { } /// <summary> /// 從新創建鏈接以前的錯誤 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void MuxerConnectionRestored(object sender, ConnectionFailedEventArgs e) { } /// <summary> /// 發生錯誤時 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void MuxerErrorMessage(object sender, RedisErrorEventArgs e) { } /// <summary> /// 更改集羣 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void MuxerHashSlotMoved(object sender, HashSlotMovedEventArgs e) { // LogHelper.WriteInfoLog("HashSlotMoved:NewEndPoint" + e.NewEndPoint + ", OldEndPoint" + e.OldEndPoint); } /// <summary> /// redis類庫錯誤 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void MuxerInternalError(object sender, InternalErrorEventArgs e) { } #endregion }
在以上RedisUtils幫助類的基礎上封裝一次調用:
/// <summary> /// Redis幫助類 /// </summary> public class RedisHelper { /// <summary> /// 緩存失效時長 /// </summary> public const int EXPIRY = 30; private static int CheckDbIndex(int dbIndex) { if (dbIndex > 16 || dbIndex < 0) { dbIndex = 0; } return dbIndex; } /// <summary> /// 獲取緩存數據 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">Redis數據庫索引</param> /// <param name="key">Redis鍵</param> /// <param name="fun">從其餘地方獲取數據源,並緩存到Redis中</param> /// <param name="timeout">過時時間,單位:分鐘</param> /// <returns></returns> public static T GetObject<T>(int dbIndex, string key, Func<T> fun, int? timeout = EXPIRY) where T : class { dbIndex = CheckDbIndex(dbIndex); T data = RedisUtils.StringGet<T>(dbIndex, key); if (data != null) { return data; } if (fun != null) { data = fun(); } if (data != null) { TimeSpan? timeSp = null; if (timeout != null) timeSp = TimeSpan.FromMinutes(Convert.ToDouble(timeout)); RedisUtils.StringSet<T>(dbIndex, key, data, timeSp); } return data; } /// <summary> /// KV /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">庫</param> /// <param name="key">鍵</param> /// <param name="func">如找不到則從func獲取</param> /// <param name="timeout">超時時間</param> /// <returns></returns> public static T GetObject_KV<T>(int dbIndex, string key, Func<T> func, TimeSpan? timeout) where T : class { T data = RedisUtils.StringGet<T>(dbIndex, key); if (data != null) { return data; } if (func != null) { data = func(); } if (data != null) { RedisUtils.StringSet<T>(dbIndex, key, data, timeout); } return data; } /// <summary> /// 異步獲取緩存數據 /// </summary> /// <typeparam name="T">數據集類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <param name="fun">從其餘地方獲取數據源,並緩存到Redis中</param> /// <param name="timeout">過時時間,單位:分鐘</param> /// <returns></returns> public static async Task<T> GetObjectAsync<T>(int dbIndex, string key, Func<T> fun, int timeout = EXPIRY) where T : class { dbIndex = CheckDbIndex(dbIndex); T data = RedisUtils.StringGet<T>(dbIndex, key); if (data != null) { return data; } if (fun != null) { data = await Task.Run(() => { return fun(); }); } if (data != null) { RedisUtils.StringSet<T>(dbIndex, key, data, TimeSpan.FromMinutes(timeout)); } return data; } /// <summary> /// 異步獲取緩存數據 /// </summary> /// <typeparam name="T">數據集類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <param name="fun">從其餘地方獲取數據源,並緩存到Redis中</param> /// <param name="timeout">過時時間,單位:分鐘</param> /// <returns></returns> public static async Task<T> GetObjectAsync<T>(int dbIndex, string key, Func<RedisCache<T>> fun, int timeout = EXPIRY) where T : class { dbIndex = CheckDbIndex(dbIndex); RedisCache<T> cache = new RedisCache<T>(); cache.CacheData = RedisUtils.StringGet<T>(dbIndex, key); if (cache.CacheData != null) { return cache.CacheData; } var temp = await Task.Run(() => { return fun(); }); if (temp != null) cache = temp; if (cache.UseCache) { RedisUtils.StringSet<T>(dbIndex, key, cache.CacheData, TimeSpan.FromMinutes(timeout)); } return cache.CacheData; } /// <summary> /// 異步獲取數據集合 /// </summary> /// <typeparam name="T">數據集類型</typeparam> /// <param name="dbIndex">數據庫</param> /// <param name="key">鍵</param> /// <param name="fun">從其餘地方獲取數據源,並緩存到Redis中</param> /// <param name="timeout">過時時間,單位:分鐘</param> /// <returns></returns> public static async Task<List<T>> GetListAsync<T>(int dbIndex, string key, Func<List<T>> fun, int timeout = EXPIRY) where T : class { dbIndex = CheckDbIndex(dbIndex); List<T> datas = RedisUtils.StringGet<List<T>>(dbIndex, key); if (datas != null && datas.Count > 0) { return datas; } datas = await Task.Run(() => { return fun(); }); if (datas != null && datas.Count > 0) { RedisUtils.StringSet<List<T>>(dbIndex, key, datas, TimeSpan.FromMinutes(timeout)); } return datas; } /// <summary> /// ZSet /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">庫</param> /// <param name="key">鍵</param> /// <param name="func">如找不到則從func獲取</param> /// <returns></returns> public static List<T> GetObject_ZSet<T>(int dbIndex, string key, Func<List<T>> func) where T : class { List<T> data = RedisUtils.SortedSetRangeByRank<T>(dbIndex, key); if (data != null && data.Count > 0) { return data; } if (func != null) { data = func(); } if (data != null) { RedisUtils.SortedSetAdd<T>(dbIndex, key, data); } return data; } /// <summary> /// Hash /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dbIndex">庫</param> /// <param name="hashID">hashID</param> /// <param name="key">鍵</param> /// <param name="func">如找不到則從func獲取</param> /// <returns></returns> public static T GetObject_Hash<T>(int dbIndex, string hashID, string key, Func<T> func) where T : class { T data = RedisUtils.HashGet<T>(dbIndex, hashID, key); if (data != null) { return data; } if (func != null) { data = func(); } if (data != null) { RedisUtils.HashSet<T>(dbIndex, hashID, key, data); } return data; } }
C# WPF 用MediaElement控件實現視頻循環播放
在WPF裏用MediaElement控件,實現一個循環播放單一視頻的程序,同時能夠控制視頻的播放、暫停、中止。
一種方式,使用MediaElement.MediaEnded事件,在視頻播放結束後,自動從新播放;
另外一種方式,使用WPF定時器,在定時器事件裏寫入視頻播放代碼。
後者優勢是能夠控制循環時長,沒必要等到視頻播放結束就能夠開始下一次播放,好比:同時啓動多個播放程序,使多個時長不一樣的視頻同時播放,無限循環,若是採用第一種方式,累計屢次自動播放後,視頻內容就沒法同步。
第一種方式:
1 XAML: 2 <MediaElement x:Name="mediaElement" HorizontalAlignment="Left" Height="261" VerticalAlignment="Top" Width="507"/> 3 <Button x:Name="btnPlay" Content="Play" HorizontalAlignment="Left" Margin="68,279,0,0" VerticalAlignment="Top" Width="75" Click="btnPlay_Click"/> 4 <Button x:Name="btnPause" Content="Pause" HorizontalAlignment="Left" Margin="170,279,0,0" VerticalAlignment="Top" Width="75" Click="btnPause_Click"/> 5 <Button x:Name="btnStop" Content="Stop" HorizontalAlignment="Left" Margin="295,279,0,0" VerticalAlignment="Top" Width="75" Click="btnStop_Click"/>
1 C#: 2 // 窗口加載事件 3 private void Window_Loaded(object sender, RoutedEventArgs e) 4 { 5 // 綁定視頻文件 6 mediaElement.Source = new Uri("D:/bird.mp4"); 7 8 // 交互式控制 9 mediaElement.LoadedBehavior = MediaState.Manual; 10 11 // 添加元素加載完成事件 -- 自動開始播放 12 mediaElement.Loaded += new RoutedEventHandler(media_Loaded); 13 14 // 添加媒體播放結束事件 -- 從新播放 15 mediaElement.MediaEnded += new RoutedEventHandler(media_MediaEnded); 16 17 // 添加元素卸載完成事件 -- 中止播放 18 mediaElement.Unloaded += new RoutedEventHandler(media_Unloaded); 19 } 20 21 /* 22 元素事件 23 */ 24 private void media_Loaded(object sender, RoutedEventArgs e) 25 { 26 (sender as MediaElement).Play(); 27 } 28 29 private void media_MediaEnded(object sender, RoutedEventArgs e) 30 { 31 // MediaElement須要先中止播放才能再開始播放, 32 // 不然會停在最後一幀不動 33 (sender as MediaElement).Stop(); 34 (sender as MediaElement).Play(); 35 } 36 37 private void media_Unloaded(object sender, RoutedEventArgs e) 38 { 39 (sender as MediaElement).Stop(); 40 } 41 42 /* 43 播放控制按鈕的點擊事件 44 */ 45 private void btnPlay_Click(object sender, RoutedEventArgs e) 46 { 47 mediaElement.Play(); 48 } 49 50 private void btnPause_Click(object sender, RoutedEventArgs e) 51 { 52 mediaElement.Pause(); 53 } 54 55 private void btnStop_Click(object sender, RoutedEventArgs e) 56 { 57 mediaElement.Stop(); 58 }
第二種方式:
注:使用DispatcherTimer,須要添加System.Windows.Threading命名空間。
1 XAML: 2 <MediaElement x:Name="mediaElement" HorizontalAlignment="Left" Height="243" Margin="19,10,0,0" VerticalAlignment="Top" Width="394" LoadedBehavior ="Manual"/> 3 <Button x:Name="btnPlay" Content="Play" HorizontalAlignment="Left" Margin="52,270,0,0" VerticalAlignment="Top" Width="75" Click="btnPlay_Click"/> 4 <Button x:Name="btnPause" Content="Pause" HorizontalAlignment="Left" Margin="163,270,0,0" VerticalAlignment="Top" Width="75" Click="btnPause_Click"/> 5 <Button x:Name="btnStop" Content="Stop" HorizontalAlignment="Left" Margin="266,270,0,0" VerticalAlignment="Top" Width="75" Click="btnStop_Click"/>
1 C#: 2 DispatcherTimer timer = new DispatcherTimer(); // 定時器timer 3 int durTime = 5; // 視頻播放時長,也就是循環週期 4 5 // 窗口加載事件 6 private void Window_Loaded(object sender, RoutedEventArgs e) 7 { 8 mediaElement.Source = new Uri("D:/bird.mp4"); // 綁定視頻文件 9 10 mediaElement.Play(); // 設置啓動播放 11 timer.Interval = new TimeSpan(0, 0, 0, durTime); // 設置定時器重複週期 12 timer.Tick += new EventHandler(timerEvent); // 設置定時器事件 13 14 timer.Start(); // 啓動定時器 15 } 16 17 // 定時器事件 18 public void timerEvent(object sender, EventArgs e) 19 { 20 // MediaElement須要先中止播放才能再開始播放, 21 // 不然會停在最後一幀不動 22 mediaElement.Stop(); 23 mediaElement.Play(); 24 } 25 26 /* 27 播放控制按鈕的點擊事件 28 */ 29 private void btnPlay_Click(object sender, RoutedEventArgs e) 30 { 31 mediaElement.Play(); // 開始播放 32 timer.Start(); // 從新啓動定時器 33 } 34 35 private void btnPause_Click(object sender, RoutedEventArgs e) 36 { 37 mediaElement.Pause(); // 暫停當前播放 38 timer.Stop(); // 中止定時器 39 } 40 41 private void btnStop_Click(object sender, RoutedEventArgs e) 42 { 43 mediaElement.Stop(); // 中止當前播放 44 timer.Stop(); // 中止定時器 45 }
項目源碼下載連接:https://files.cnblogs.com/files/walker-cheng/WpfVideoCyclePlayer.zip
net 異步與同步
1、摘論
爲何不是摘要呢?其實這個是我我的的想法,其實不少人在談論異步與同步的時候都忽略了,同步異步不是軟件的原理,其自己是計算機的原理及概念,這裏就不過多的闡述計算機原理了。在學習同步與異步以前,咱們須要先研究幾個問題
在說到異步前,先來理一下幾個容易混淆的概念,並行、多線程、異步。
並行,通常指並行計算,是說同一時刻有多條指令同時被執行,這些指令可能執行於同一CPU的多核上,或者多個CPU上,或者多個物理主機甚至多個網絡中。
多線程,通常指同一進程中多個線程(包含其數據結構、上下文與代碼片斷)協做運行。在多核計算機中多個線程將有機會同時運行於多個核上,若是線程中進行的是計算,則行成並行計算。
異步,與同步相對應,是指呼叫另外一操做後,不等待其結果,繼續執行以後的操做,若以後沒有其餘操做,當前線程將進入睡眠狀態,而CPU時間將有機會切至其餘線程。在異步操做完成後經過回調函數的方式獲取通知與結果。異步的實現方式有多種,如多線程與完成端口。多線程將異步操做放入另外一線程中運行,經過輪詢或回調方法獲得完成通知;完成端口,由操做系統接管異步操做的調度,經過硬件中斷,在完成時觸發回調方法,此方式不須要佔用額外線程。
經過上面的兩張圖,能夠把三個概念透析的很是好理解,異步在某種意義上講是「時空轉換」即時間換空間,空間換時間。下邊咱們來學習下,在net 中的異步
2、同步和異步
1.同步執行
爲了準備一個耗時的程序,本人準備了一本Txt修仙小說,咱們用程序讀取一行行輸出,輸出完成之後,咱們輸出一句話,"今天書就讀到這裏吧!!累了,休息一會,休息一會!一休哥",爲了更好的演示同步異步,本文采用winform程序,同時爲了體驗winform 和控制檯 帶來的視覺效果,咱們選擇項目屬性,應用程序這選擇控制檯。
在準備一個很費時的讀書方法,
/// <summary> /// 讀書,一個很廢時間的任務 /// </summary> public void ReadBook() { //咱們能夠經過 Thread.CurrentThread.ManagedThreadId 獲取當前線程的惟一標識符 Console.WriteLine("********************** ReadBook Start【" + Thread.CurrentThread.ManagedThreadId + "】等待............... **********************************************"); System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); string Path= AppDomain.CurrentDomain.BaseDirectory + "zqjz.txt"; List<string> list = new List<string>(); System.IO.StreamReader sr = new System.IO.StreamReader(Path, Encoding.Default); string line = ""; Console.ForegroundColor = ConsoleColor.Black; while ((line = sr.ReadLine()) != null&& list.Count<120) { char[] array= line.ToArray(); for (int i = 0; i < array.Length; i++) { Console.Write(array[i]); if (i!=0) { // Thread.Sleep(128);//人眼最快敏感視覺是128毫秒左右,咱們這裏測試先使用10毫秒 Thread.Sleep(10); } } Console.WriteLine(); Console.BackgroundColor = (ConsoleColor)new Random().Next(3, 15); list.Add(line); } sr.Close(); sr.Dispose(); watch.Stop(); Console.WriteLine("今天讀書用了"+ watch.ElapsedMilliseconds+"豪秒"); Console.WriteLine("********************** ReadBook End【" + Thread.CurrentThread.ManagedThreadId + " 】**********************************************"); }
這個方法比較簡單,就是讀取電子書,同時給方法加上了耗時記錄,和當前線程的惟一標識。如今咱們在窗體上加上一個buttion 調用下咱們的讀書。看看結果是怎麼樣的,同時建議打開任務管理器,監控下CPU,等cpu 平穩之後,咱們在點擊同步執行按鈕。「如今是咱們本身在讀書」。
2.異步執行
關於異步在前邊的摘論裏面介紹了大概,這裏不過多演示,請繼續看!在早期,net 的異步都是在使用委託來作的,而委託使用的是線程池ThreadPool來實現的,曾取下一篇文章介紹線程,到時候在詳細介紹線程池,關於委託請觀看本人前邊的文章 "linq to Objet",咱們在程序上在加上一個按鈕,裏面老師讀書,個人心缺飛了,在想下課玩什麼?怎麼和同窗玩。
private void btnSync_Click(object sender, EventArgs e) {//同步 Console.WriteLine("**********************btnSync_Click Start【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************"); ReadBook(); MessageBox.Show("今天書就讀到這裏吧!!累了,休息一會,休息一會!一休哥"); Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************"); } private void btnasync_Click(object sender, EventArgs e) {//異步 Console.WriteLine("**********************btnSync_Click Start【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************"); Action action = new Action(() => ReadBook()); action.BeginInvoke(null,null);//參數先無論,咱們先給null,一會咱們會繼續演示 MessageBox.Show("今天想玩,怎麼騙過老師呢!!書還在繼續讀,可是我已經在玩了!!!"); Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************"); }
上面代碼分別爲異步調用和同步調用,下圖爲異步調用結果,咱們會發現,異步調用窗體是能夠移動的,而且CPU 會有很大的波峯,細心的人會發現,執行時間是同樣的,只是他們的線程惟一標識是不同的。
經過上述演示,異步和同步的區別很簡單了吧!這裏就不過多描述,本身總結。可是咱們的要說下異步和多線程的區別?其實異步只是一種結果(目地),而多線程纔是實現這種結果的一種方式,在NET 裏面,異步和多線程沒有本質的區別,我的總結惟一的區別就是,應用場景不一樣。
重點:多播委託不能夠指定異步。不予顯示,本身去嘗試和找尋原理,實在找不到原理能夠理解爲這是任何高級語言的一個規定。有關多播委託請參考本人:一步一步帶你瞭解 Linq to Object
3、異步回掉和異步等待(阻塞)
1.異步回掉:
剛纔咱們一直在上課讀書,可是個人內心在想的是下課去哪裏玩,如何玩?這個時候,咱們須要在異步讀書的方法以後也就是下課之後再去玩。看下代碼是怎麼寫的。
//異步 Console.WriteLine("**********************btnSync_Click Start【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************"); #region 異步回調 IAsyncResult iAsyncResult = null; AsyncCallback callback = t => { Console.WriteLine(t); Console.WriteLine("下邊代碼是比較兩個對象是否同樣"); Console.WriteLine($"string.ReferenceEquals(t, iAsyncResult)={string.ReferenceEquals(t, iAsyncResult)}"); Console.WriteLine($"當前線程ID {Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"終於下課了!咱們走吧,盡情的玩吧,你問我老師講的啥,我知道!!"); };//AsyncCallback 自己就是一個委託,這個委託有一個參數,這個參數就是咱們委託的BeginInvoke的返回值。咱們使用這個委託去作異步回調 #endregion Action action = () => ReadBook();//簡寫 iAsyncResult= action.BeginInvoke(callback, null);//這裏的第一個參數,咱們就是異步回調 MessageBox.Show("今天想玩,怎麼騙過老師呢,下課玩點什麼呢!!書還在繼續讀,可是個人心已經飛了!!!"); Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
所謂的異步回調,就是在異步線程執行完在執行的代碼塊。
2.異步等待(阻塞):
主線程等待子線程有這麼幾種方式:
1.主線程等待子線程的時候有返回,好比說咱們常見的進度條功能,執行一點我就返回下。2.主線程等待子線程有時間限制,例如:中午放學我等你五分鐘,你要是不完事,我就先吃飯去了。3.主線程等待子線程無返回,好比死等,今天的代碼我學不會了,我就不睡覺了。下面咱們分別看看這三種狀況。咱們管操做線程等待的過程叫作阻塞(se)進程.阻塞主進程之後等待子線程的執行,咱們成爲線程的阻塞,
剛纔咱們是使用回調,在異步執行完成,才執行了一個代碼塊,這個時候messagebax 已經輸出了,如今咱們開看看課堂下的學生表現。「將下列代碼放到咱們 MessageBox.Show("今天想玩,怎麼騙過老師呢,下課玩點什麼呢!!書還在繼續讀,可是個人心已經飛了!!!");」以後,咱們來看看
while (!iAsyncResult.IsCompleted)//邊等待邊操做,能夠用於作進度條 { Thread.Sleep(100);//建議控制100毫秒一次 Console.WriteLine("老師還在教咱們讀書.....請等待..............."); } //當異步完成之後,咱們在執行下邊的這句話 Console.WriteLine("學生甲:衝啊..............打籃球全"); Console.WriteLine("學生乙:王美美.......我愛你!我們交往吧....*#*#*#**??!"); Console.WriteLine("學生丙:呼呼呼呼呼呼呼呼。。。。。嚕。。。。。。。。。。今天的肉丸子真好吃,真但願這不是夢啊"); Console.WriteLine("學生丁:大海啊,就像媽媽同樣,海浪啊!你爲啥這麼猛!老是在我人生巔峯......被打斷"); Console.WriteLine("學生丙:別BiBi了,海浪是你後媽,滾一邊去淫詩去!別TMD打擾老子睡覺");
剛纔執行的線程等待在阻塞的過程當中是有損耗的,咱們損耗 的是時間,因此回調會在子線程以前執行,那麼咱們想要無損耗怎麼去寫,怎麼去阻塞咱們的主線程呢 「 bool RunBool = iAsyncResult.AsyncWaitHandle.WaitOne();」; 當子線程執行成功了,就會返回TRUE,當子線程執行過程當中出現exection 之後,就返回false;
這種寫法主線程就沒法返回了。可是咱們能夠新創建一個線程去監控子線程。這裏就不寫那麼複雜了。
第二種狀況,我只等你兩秒鐘,有時間限制的阻塞
#region 異步等待1 有損耗 帶返回 //while (!iAsyncResult.IsCompleted)//邊等待邊操做,能夠用於作進度條 //{ // Thread.Sleep(100);//建議控制100毫秒一次 // Console.WriteLine("老師還在教咱們讀書.....請等待..............."); //} #endregion #region 異步等待2 無損耗 無返回 //bool RunBool = iAsyncResult.AsyncWaitHandle.WaitOne();//返回結果是子線程執行成功或者失敗,不是實時返回的。 //iAsyncResult.AsyncWaitHandle.WaitOne(-1);//寫法2 #endregion #region 有時間限制的異步等待 iAsyncResult.AsyncWaitHandle.WaitOne(2000);//我最多等你2秒鐘,若是你提早完事,咱們提早走 #endregion //當異步完成之後,咱們在執行下邊的這句話 Console.WriteLine("學生甲:衝啊..............打籃球全"); Console.WriteLine("學生乙:王美美.......我愛你!我們交往吧....*#*#*#**??!"); Console.WriteLine("學生丙:呼呼呼呼呼呼呼呼。。。。。嚕。。。。。。。。。。今天的肉丸子真好吃,真但願這不是夢啊"); Console.WriteLine("學生丁:大海啊,就像媽媽同樣,海浪啊!你爲啥這麼猛!老是在我人生巔峯......被打斷"); Console.WriteLine("學生丙:別BiBi了,海浪是你後媽,滾一邊去淫詩去!別TMD打擾老子睡覺"); Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
這種子線程執行兩秒之後,主線程在執行這個問題常常會在面試裏面問。面試常常會問,主線程A 至少要執行10,秒,子線程B至少要執行30秒,如何讓主線程在子線程執行20秒開始執行。
下邊咱們就舉例,代碼不會,我就要學習了學習不會就不睡覺,就死學到底了。
#region 異步等待死等 //死等就是,只要你不異常,就必須給我一個結果,好比學習,必須學會爲止 action.EndInvoke(iAsyncResult);//EndInvoke 的返回值取決與你的委託!你的委託有返回值,我就有返回值。 #endregion
注意圖上反應的問題。其實回調執行的是子線程。咱們死等(阻塞 主線程等待子線程)的是子線程,而不是子線程的回調。這個時候是主線程和子線程一塊兒執行的(線程的無序)。這就會照成CPU 更大的波峯,很容易宕機。因爲演示這種結果不容易,須要執行不少遍,這裏沒有截取到CPU 波峯。本人I7 CPU 基本都趕到頂了。
經過上圖能夠看出,主線程和子線程的執行前後順序不必定誰前後,線程是無序的。
若是下了本文demo 的同窗會發現,這個時候UI 是卡住的,主窗體UI阻塞,因此窗體是沒法移動的。
。到這裏異步咱們就學習完了,下邊總結下
4、總結
1.異步等待和異步回調的區別?面試會考的哦!!
答:異步等待是在子線程執行完成之後回到主線程,j解除主線程的阻塞繼續執行,而異步回調是子線程執行完成之後在去以子線程再去執行的任務代碼塊。
異步等待卡主線程,回調不卡主線程。
在委託中回調不能夠取得子線程執行的結果,等待能夠經過線程狀態參數取得執行結果。
2.主線程A 須要執行1秒,而子線程B須要執行3秒。若是讓B執行2秒之後在執行?或者 接口A 調用5秒沒結果,我就調用接口B去取數據?在接口B取到數據之後,接口若是也取到數據,仍然使用結果B的,怎麼去作。
答:使用 iAsyncResult.AsyncWaitHandle.WaitOne(2000);
關於接口(WebApi ,Service)的狀況,咱們也是須要使用線程等待,可是這個時候咱們就要加鎖或者加計時器 StopWatch 去作。關於鎖之後在談。可是加鎖會影響效率,計時器在多服務狀況下還不許確,這是大多數面試者的回答。
咱們把沒有演示的一點點知識在這裏演示下。
咱們一直沒有說這個參數有什麼作用,這裏簡單介紹下。當我線程啓動的時候,我能夠啓動多條線程,可是我沒法肯定那個線程執行的過程,這個時候咱們能夠經過這個參數傳遞線程狀態。這裏不過多解釋。有用到的私聊本人。
3.若是我想使用子線程的結果去作主線程的參數,如何去作。請說明你的理由。這裏不過多解釋了,案列很清晰。
4.這裏的阻塞是卡主線程的,咱們如何不卡主線程??
下節多線程中找答案。
我的總結:
1.net 異步支持
Net framework可讓你異步調用任何方法。爲達這樣的目的,你能夠定義一個與你要調用的方法的簽名相同的委託。公共語言運行時將自動爲該委託定義與簽名相同的BeginInvok和EndInvoke方法。
異步委託調用BeginInvok和EndInvoke方法,但在.NET Compact Framework中並不支持。
.NET Framework 容許您異步調用任何方法。定義與您須要調用的方法具備相同簽名的委託;公共語言運行庫將自動爲該委託定義具備適當簽名
的 BeginInvoke 和 EndInvoke 方法。
BeginInvoke 方法用於啓動異步調用。它與您須要異步執行的方法具備相同的參數,只不過還有兩個額外的參數(將在稍後描述)。
BeginInvoke 當即返回,不等待異步調用完成。
BeginInvoke 返回 IasyncResult,可用於監視調用進度。
EndInvoke 方法用於檢索異步調用結果。調用 BeginInvoke 後可隨時調用 EndInvoke 方法;若是異步調用未完成,EndInvoke 將一直阻塞到
異步調用完成。EndInvoke 的參數包括您須要異步執行的方法的 out 和 ref 參數(在 Visual Basic 中爲 <Out> ByRef 和 ByRef)以及由
BeginInvoke 返回的 IAsyncResult。
四種使用 BeginInvoke 和 EndInvoke 進行異步調用的經常使用方法。調用了 BeginInvoke 後,能夠:
1.進行某些操做,而後調用 EndInvoke 一直阻塞到調用完成。
2.使用 IAsyncResult.AsyncWaitHandle 獲取 WaitHandle,使用它的 WaitOne 方法將執行一直阻塞到發出 WaitHandle 信號,而後調用
EndInvoke。這裏主要是主程序等待異步方法,等待異步方法的結果。
3.輪詢由 BeginInvoke 返回的 IAsyncResult,IAsyncResult.IsCompeted肯定異步調用什麼時候完成,而後調用 EndInvoke。此處理我的認爲與
相同。
4.將用於回調方法的委託傳遞給 BeginInvoke。該方法在異步調用完成後在 ThreadPool 線程上執行,它能夠調用 EndInvoke。這是在強制裝
換回調函數裏面IAsyncResult.AsyncState(BeginInvoke方法的最後一個參數)成委託,而後用委託執行EndInvoke。
警告 始終在異步調用完成後調用 EndInvoke。
經過EndInvoke方法檢測異步調用的結果。若是異步調用還沒有完成,EndInvoke將阻塞調用線程,直到它完成。EndInvoke參數包括out和ref參數,本文沒有講到,另外本文沒有演示EndInvoke 返回值 。
2.同步方法和異步方法的區別
同步方法調用在程序繼續執行以前須要等待同步方法執行完畢返回結果
異步方法則在被調用以後當即返回以便程序在被調用方法完成其任務的同時執行其它操做
Demo 下載
88.睡覺