.NET并發(fā)編程之函數(shù)閉包
函數(shù)式編程
一個(gè)函數(shù)輸出當(dāng)做另一個(gè)函數(shù)輸入。有時(shí)候一個(gè)復(fù)雜問題,我們拆分成很多個(gè)步驟函數(shù),這些函數(shù)組合起來調(diào)用解決一個(gè)復(fù)雜問題。
在C#中不支持函數(shù)組合,但可以直接像這樣調(diào)用B(A(n)),這也是函數(shù)組合,但這不利于閱讀,人們習(xí)慣從左往右閱讀,而不是相反的方向。通過創(chuàng)建擴(kuò)展方法可以任何組合兩個(gè)函數(shù),像下面這樣
Func<A,C> Compose<A,B,C>(this Func<A.B> f ,Func<B,C> g)=>(n)=>g(f(n))
Func<a,b>
創(chuàng)建了一個(gè)擴(kuò)展Compose的擴(kuò)展方法,以泛型委托Func<b,c>
為輸入?yún)?shù),返回組合后的函數(shù)Func<a,c>
。創(chuàng)建一個(gè)高階函數(shù)Compose把不利于閱讀的隱藏起來。
在F#中就非常方便的使用函數(shù)組合。舉個(gè)例子,將一個(gè)列表中數(shù)字增加4再乘以3,構(gòu)建這兩個(gè)步驟的函數(shù)(當(dāng)然利用C#linq或F#map可以直接(x+4)*3,這里主要演示兩個(gè)功能函數(shù)如何組合起來)。
letadd4x=x+4 letmulitply3x=x*3 letlist=[0..10] letnewList=List.map(funx->mulitply3(add4(x)))list letnewList2=list|>List.map(add4>>mulitply3
在F#中使用>>中綴運(yùn)算符來使函數(shù)組合可以從左到右閱讀,更加精煉、簡(jiǎn)潔。
閉包的應(yīng)用
閉包可以讓函數(shù)訪問其所在的外部函數(shù)中的參數(shù)和變量,即使在其外部函數(shù)被返回之后。在js中經(jīng)常會(huì)出現(xiàn)閉包的場(chǎng)景,在C#和F#中,編譯器使用閉包來增加和擴(kuò)展變量的范圍。
C#在.NET2.0后引入閉包。在lambda和匿名方法中得到充分的使用。像下面的匿名函數(shù)引用變量a,訪問和管理變量a的狀態(tài)。如果不用閉包,就需要額外創(chuàng)建一個(gè)類函數(shù)來調(diào)用。
strings="freevariable"; Func<string,string>lambda=value=>a+""+value;
以下載圖片更新窗體PictureBox控件為例:
void UpdateImage(string url) { System.Windows.Forms.PictureBox picbox = this.pictureBox1; var client = new WebClient(); client.DownloadDataCompleted += (o, e) => { if (picbox != null) { using (var ms = new MemoryStream(e.Result)) { picbox.Image = Image.FromStream(ms); } } }; client.DownloadDataAsync(new Uri(url)); //picbox = null; }
因?yàn)槭钱惒较螺d,UPdateImage方法返回后,圖片還未下載完成,但picbox變量仍然可以使用。這就是變量捕獲。lambda表達(dá)式捕獲了局部變量image,因此它仍停留在作用域中。但捕獲的變量值是在運(yùn)行時(shí)確定的,而不是在捕獲時(shí),最后一句如果放開,將不能更新窗體。運(yùn)行時(shí)picbox為null了,在F#中不存在null的概念,所以也不會(huì)出現(xiàn)此類錯(cuò)誤。
多線程環(huán)境中的閉包使用。猜測(cè)下面的代碼運(yùn)行結(jié)果如何?
for (int i = 1; i < 10; i++) { Task.Factory.StartNew(()=>Console.WriteLine("{0}-{1}", Thread.CurrentThread.ManagedThreadId,i)); }
不會(huì)按期望的那樣打印1-9,因?yàn)樗麄児蚕碜兞縤,調(diào)用時(shí)i的值可能已經(jīng)被循環(huán)修改了。印證上面說的捕獲的變量值是在運(yùn)行時(shí)確定的。
這種情況就很難搞,給并行編程帶來了頭疼的問題,變量可變,這不廢話嗎,變量不會(huì)變就不叫變量了。在C#中解決此類問題的一個(gè)方法就是為每個(gè)任務(wù)創(chuàng)建創(chuàng)建和捕獲一個(gè)新的臨時(shí)變量,這樣它就能保留捕獲時(shí)的值。在F#中不存在這個(gè)問題,它的For循環(huán)每次創(chuàng)建一個(gè)新的不可變值。
記憶化函數(shù)緩存
一些函數(shù)會(huì)頻繁的使用相同的參數(shù)去調(diào)用。我們可以將用相同的參數(shù)調(diào)用函數(shù)的結(jié)果存儲(chǔ)起來,以便下次調(diào)用直接返回結(jié)果。例如對(duì)圖片每個(gè)像素做處理,一張圖片可能相同像素的會(huì)有很多,通過緩存可以直接返回上次計(jì)算結(jié)果。
//簡(jiǎn)單的函數(shù)緩存 public static Func<T, R> Memoize<T, R>(Func<T, R> func) where T : IComparable { Dictionary<T, R> cache = new Dictionary<T, R>(); return arg => { if (cache.ContainsKey(arg)) return cache[arg]; return (cache[arg] = func(arg)); }; } // 線程安全的函數(shù)緩存 public static Func<T, R> MemoizeThreadSafe<T, R>(Func<T, R> func) where T : IComparable { ConcurrentDictionary<T, R> cache = new ConcurrentDictionary<T, R>(); return arg => cache.GetOrAdd(arg, a => func(a)); } // 利用延遲提高性能的函數(shù)緩存 public static Func<T, R> MemoizeLazyThreadSafe<T, R>(Func<T, R> func) where T : IComparable { ConcurrentDictionary<T, Lazy<R>> cache = new ConcurrentDictionary<T, Lazy<R>>(); return arg => cache.GetOrAdd(arg, a => new Lazy<R>(() => func(a))).Value; }
上述示例代碼中有三個(gè)版本的函數(shù)記憶化。調(diào)用像下面這樣
public static string Greeting(string name) { return $"Warm greetings {name}, the time is {DateTime.Now.ToString("hh:mm:ss")}"; } public static void RunDemoMemoization() { var greetingMemoize = Memoize<string, string>(Greeting); Console.WriteLine(greetingMemoize("Richard")); Console.WriteLine(greetingMemoize("Paul")); Console.WriteLine(greetingMemoize("Richard")); }
線程安全字典ConcurrentDictionary可以保證只向集合里添加一個(gè)相同值,但函數(shù)求值可能會(huì)被執(zhí)行多次,所以利用.NET4之后的延遲對(duì)象加載技術(shù)。在真正需要使用對(duì)象時(shí)候才去實(shí)例化(通過訪問延遲對(duì)象的Value屬性),而且是線程安全的。
到此這篇關(guān)于.NET并發(fā)編程之函數(shù)閉包的文章就介紹到這了,更多相關(guān).NET函數(shù)閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java同步容器和并發(fā)容器詳解
- Java從同步容器到并發(fā)容器的操作過程
- Java并發(fā)編程之CountDownLatch源碼解析
- Java并發(fā)編程之Semaphore的使用簡(jiǎn)介
- Java并發(fā)編程之線程之間的共享和協(xié)作
- Java并發(fā)編程之Exchanger方法詳解
- Java 并發(fā)編程中如何創(chuàng)建線程
- 詳解Java并發(fā)編程之內(nèi)置鎖(synchronized)
- Java 并發(fā)編程ArrayBlockingQueue的實(shí)現(xiàn)
- Java并發(fā)編程之常用的輔助類詳解
- Java并發(fā)編程之同步容器
相關(guān)文章
淺談Asp.Net母版頁和內(nèi)容頁運(yùn)行機(jī)制
這篇文章主要介紹了淺談Asp.Net母版頁和內(nèi)容頁運(yùn)行機(jī)制,詳細(xì)的介紹了母版頁和內(nèi)容頁的運(yùn)行過程步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11asp.net基于session實(shí)現(xiàn)購(gòu)物車的方法
這篇文章主要介紹了asp.net基于session實(shí)現(xiàn)購(gòu)物車的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了asp.net使用session存儲(chǔ)臨時(shí)數(shù)據(jù)實(shí)現(xiàn)購(gòu)物車功能的相關(guān)技巧,需要的朋友可以參考下2015-11-11詳解ASP.NET數(shù)據(jù)綁定操作中Repeater控件的用法
.NET中的Repeater控件支持?jǐn)?shù)據(jù)模板,而且可以自由地定義樣式,這里我們就來詳解ASP.NET數(shù)據(jù)綁定操作中Repeater控件的用法,需要的朋友可以參考下2016-06-06.NET 刷新頁面防止表單二次提交的實(shí)現(xiàn)方法
頁面上按鈕是服務(wù)器控件,現(xiàn)在刷新頁面要防止按鈕事件重復(fù)執(zhí)行。這篇文章給大家?guī)砹?net刷新頁面防止表單二次提交的實(shí)現(xiàn)方法,非常不錯(cuò),感興趣的朋友一起看看吧2016-09-09ASP.NET操作Word的IIS權(quán)限設(shè)置
檢索 COM 類工廠中 CLSID 為 {00024500-0000-0000-C000-000000000046} 的組件時(shí)失敗,原因是出現(xiàn)以下錯(cuò)誤: 80070005。2011-02-02