本系列学习在.NET中的并发并行编程模式,实战技巧

内容目录

函数式编程闭包的应用记忆化函数缓存

函数式编程

一个函数输出当做另一个函数输入。有时候一个复杂问题,我们拆分成很多个步骤函数,这些函数组合起来调用解决一个复杂问题。

在C#中不支持函数组合,但可以直接像这样调用B(A(n)),这也是函数组合,但这不利于阅读,人们习惯从左往右阅读,而不是相反的方向。通过创建扩展方法可以任何组合两个函数,像下面这样

Func<A,C> Compose<A,B,C>(this Func<A.B> f ,Func<B,C> g)=>(n)=>g(f(n))

上述代码为泛型委托Func<a,b style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">创建了一个扩展Compose的扩展方法,以泛型委托Func<b,c style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">为输入参数,返回组合后的函数Func<a,c style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">。创建一个高阶函数Compose把不利于阅读的隐藏起来。

在F#中就非常方便的使用函数组合。举个例子,将一个列表中数字增加4再乘以3,构建这两个步骤的函数(当然利用C#linq或F#map可以直接(x+4)*3,这里主要演示两个功能函数如何组合起来)。

let add4 x=x+4
let mulitply3 x=x*3
let list=[0..10]
let newList=List.map(fun x->mulitply3(add4(x))) list
let newList2=list |>List.map(add4>>mulitply3

在F#中使用>>中缀运算符来使函数组合可以从左到右阅读,更加精炼、简洁。

闭包的应用

闭包可以让函数访问其所在的外部函数中的参数和变量,即使在其外部函数被返回之后。在js中经常会出现闭包的场景,在C#和F#中,编译器使用闭包来增加和扩展变量的范围。

C#在.NET2.0后引入闭包。在lambda和匿名方法中得到充分的使用。像下面的匿名函数引用变量a,访问和管理变量a的状态。如果不用闭包,就需要额外创建一个类函数来调用。

string s= "free variable";
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;
}

因为是异步下载,UPdateImage方法返回后,图片还未下载完成,但picbox变量仍然可以使用。这就是变量捕获。lambda表达式捕获了局部变量image,因此它仍停留在作用域中。但捕获的变量值是在运行时确定的,而不是在捕获时,最后一句如果放开,将不能更新窗体。运行时picbox为null了,在F#中不存在null的概念,所以也不会出现此类错误。

多线程环境中的闭包使用。猜测下面的代码运行结果如何?

for (int i = 1; i < 10; i++)
{
    Task.Factory.StartNew(()=>Console.WriteLine("{0}-{1}",
        Thread.CurrentThread.ManagedThreadId,i));
}

不会按期望的那样打印1-9,因为他们共享变量i,调用时i的值可能已经被循环修改了。印证上面说的捕获的变量值是在运行时确定的。

这种情况就很难搞,给并行编程带来了头疼的问题,变量可变,这不废话吗,变量不会变就不叫变量了。在C#中解决此类问题的一个方法就是为每个任务创建创建和捕获一个新的临时变量,这样它就能保留捕获时的值。在F#中不存在这个问题,它的For循环每次创建一个新的不可变值。

记忆化函数缓存

一些函数会频繁的使用相同的参数去调用。我们可以将用相同的参数调用函数的结果存储起来,以便下次调用直接返回结果。例如对图片每个像素做处理,一张图片可能相同像素的会有很多,通过缓存可以直接返回上次计算结果。

//简单的函数缓存
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));                
    };
}

// 线程安全的函数缓存
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));
}

// 利用延迟提高性能的函数缓存
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;
}

上述示例代码中有三个版本的函数记忆化。调用像下面这样

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可以保证只向集合里添加一个相同值,但函数求值可能会被执行多次,所以利用.NET4之后的延迟对象加载技术。在真正需要使用对象时候才去实例化(通过访问延迟对象的Value属性),而且是线程安全的。

补充一个,以前写设计模式时,单例模式的创建方式,第四种,延迟+线程安全的单例模式传送门,设计模式速查手册

 public sealed class Singleton
 {
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton(), true); //#A

    public static Singleton Instance => lazy.Value;

    private Singleton()
    { }
}

to be contiued!
下集:不可变性

写给普通:

花有重开日,人无再少年

小时候快乐是本能,长大后快乐是本事

.NET并发编程-函数闭包的更多相关文章

  1. .NET并发编程-任务函数并行

    本系列学习在.NET中的并发并行编程模式,实战技巧 请问普通: 被门夹过的核桃还能补脑吗 本小节开始学习基于任务的函数式并行.本系列保证最少代码呈现量,虽然talk is cheap, show me ...

  2. 并发编程: c++11 thread(Func, Args...)利用类成员函数创建线程

    c++11是VS2012后支持的新标准,为并发编程提供了方便的std::thread. 使用示例: #include <thread> void thread_func(int arg1, ...

  3. Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程

    Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...

  4. Scala并发编程

    Scala的actor提供了一种基于事件的轻量级线程.只要使用scala.actors.Actor伴生对象的actor方法,就可以创建一个actor.它接受一个函数值/闭包做参数,一创建好就开始运行. ...

  5. [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念

      本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程?   java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...

  6. 并发编程概述--C#并发编程经典实例

    优秀软件的一个关键特征就是具有并发性.过去的几十年,我们可以进行并发编程,但是难度很大.以前,并发性软件的编写.调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程.新版.NET 中的程序库和语 ...

  7. [译] Go 并发编程基础

    原文:Fundamentals of concurrent programming 译者:youngsterxyf 本文是一篇并发编程方面的入门文章,以Go语言编写示例代码,内容涵盖: 运行期并发线程 ...

  8. Go并发编程基础(译)

    2015-05-20 三 By youngsterxyf 原文:Fundamentals of concurrent programming 译者:youngsterxyf 本文是一篇并发编程方面的入 ...

  9. Go part 8 并发编程,goroutine, channel

    并发 并发是指的多任务,并发编程含义比较广泛,包含多线程.多进程及分布式程序,这里记录的并发是属于多线程编程 Go 从语言层面上支持了并发的特性,通过 goroutine 来完成,goroutine ...

随机推荐

  1. 【Docker】runtime create failed: container_linux.go:345: 解决

    ------------------------------------------------------------------------------------------------- | ...

  2. 【开源】我和 JAP(JA Plus) 的故事

    JA Plus 故事 程序员的故事如此简单之绕不过去的开源情结 我们准备做一件伟大的事,也可以说是一件真真正正普惠的事. 絮 是的,你没有看错,就是"絮"而非"序&quo ...

  3. wmic 操作文件的datafile

    wmic datafile /?动词有ASSOC,CALL,CREATE,DELETE,GET,LIST 这几个 命令:wmic datafile where "filename='dsc0 ...

  4. [Usaco2015 dec]Breed Counting

    原题链接https://www.lydsy.com/JudgeOnline/problem.php?id=4397 用线段树维护区间和即可.时间复杂度\(O((N+Q)logN)\). #includ ...

  5. bootstrap 后端模板

    Twitter Bootstrap 框架已经广为人知,用于加快网站,应用程序或主题的界面开发,并被公认为是迄今对于 Web 开发的最有实质性帮助的工具之一.在此之前的,各种各样的界面库伴随着高昂的维护 ...

  6. Mac中安装Git

    Mac 安装git 打开Mac终端输入git命令 如果出现以下代码说明已经安装 usage: git [--version] [--help] [-C <path>] [-c <na ...

  7. django之orm单表查询

    这几天重新学习了一下django的orm,以此作为记录来分享. Part1:修改配置,生成表 在写数据和查数据之前,首先先得把django配置一下,具体配置如下: 1.先在公共项目的settings中 ...

  8. U盘UEFI+GPT模式安装CentOS7.X系统

    1.制作CentOS7安装盘 还是老套路,开局先制作安装盘,UltraISO软碟通,上图   (1) 打开UltraISO软件,选择"文件"-> "打开" ...

  9. Java 常见关键字总结:final、static、this、super!

    final,static,this,super 关键字总结 final 关键字 final关键字,意思是最终的.不可修改的,最见不得变化 ,用来修饰类.方法和变量,具有以下特点: final修饰的类不 ...

  10. MongoTemplate聚合(一)$lookup

    mongodb   最近入职了新的公司,新公司统一使用的mongodb,es等非关系型数据库.以前对es有一些了解,其实就是灵活的文档类型结构,不受限于关系型数据库的那种字段唯一确定的"死板 ...