本系列学习在.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. kubernets之job资源

    一  介绍job资源 1.1   前面介绍的RC,RS,DS等等,管控的pod都是需要长期持久的运行的应用,但是尝试考虑另外一种场景,在微服务的场景下,有些pod的作用就是需要 执行完一些命令之后正常 ...

  2. 奇技淫巧,还是正统功夫? - Python推导式最全用法

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 python免费学习资 ...

  3. STL_map和multimap容器

    一.map/multimap的简介 map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对.它提供基于key的快速检索能力. map中key值是唯一的.集合中的元素按一定的顺 ...

  4. jmeter-命令行执行及测试报告导出

    问题1:GUI方式能够进行测试报告导出? 回答:目前找了很多资料,没有找到采用GUI方式测试完成,然后命令方式导出测试报告: 问题2:命令行导出测试报告的前提都有啥?---- 这里参考了老_张大大的博 ...

  5. mybatis源码解析之架构理解

    mybatis是一个非常优秀的开源orm框架,在大型的互联网公司,基本上都会用到,而像程序员的圣地-阿里虽然用的是自己开发的一套框架,但其核心思想也无外乎这些,因此,去一些大型互联网公司面试的时候,总 ...

  6. JMeter去掉启动的cmd命令窗口和制作快捷方式

    1.去掉启动的cmd命令窗口 在使用jmeter时,如果使用默认的jmeter.bat启动的话,会出现一个CMD命令窗口,之后才会启动jmeter工作界面: 如果直接启用ApacheJMeter.ja ...

  7. loj10095 间谍网络

    题目描述由于外国间谍的大量渗入,国家安全正处于高度危机之中.如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B.有些间谍接受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报.所 ...

  8. Redis 实战 —— 11. 实现简单的社交网站

    简介 前面介绍了广告定向的实现,它是一个查询密集型 (query-intensive) 程序,所以每个发给它的请求都会引起大量计算.本文将实现一个简单的社交网站,则会尽可能地减少用户在查看页面时系统所 ...

  9. Java,Scala:JDBCUtil,MySqlUtil,PhoenixJDBC

    Java,Scala:JDBCUtil,MySqlUtil,PhoenixJDBC pom.xml添加依赖 Java:方式一(亲测实用) 方式二:Scala 方式三:Java PhoenixJDBCU ...

  10. VMware vCenter 6.0 安装及群集配置介绍(转载)

    转载自http://blog.51cto.com/wzlinux/2094598 一.介绍 VMware vCenter Server 提供了一个可伸缩.可扩展的平台,为虚拟化管理奠定了基础.可集中管 ...