响应式编程(Reactive programming)

响应式编程是指确保程序对事件或输入做出响应的做法。在这一节,我们将专注于图形界面方面的响应式编程,图形界面总量响应式的。然而,其他网格的编程也需要考虑响应式编程,例如,运行在服务器上的程序总是需要保持对输入作出响应,即使是在它处理其他需要长时间运行的任务期间。我们将在第十一章实现聊天服务器时,会看到在服务器编程方面也要用到这里讨论的一些方法。

大多数图形界面库使用事件循环去处理绘制图形界面,以及与用户之间的交互,就是说,一个线程既要负责绘制图形界面,也要处理其上的所有事件,我们称这种线程为图形界面线程。另一种考虑:只应该用图形界面线程更新图形界面对象,要避免发生其他可能破坏这个图形界面线程状态的情况出现,即,在其他线程中非常耗时的计算或输入输出操作,不应该出现在图形界面线程中。如果这个图形界面线程需要进行长时间运行的计算,它既不可能与用户交互,也不可绘制图形界面,这就是图形界面反应迟钝的头号原因。

可以看到,在下面的示例中创建的图形界面很容易就能变得没有反映,因为在这个图形界面线程中有太多的计算。我们首选来看一下一个有用的抽象概念,后台辅助线程(BackgroundWorker)类,在System.ComponentModel 命名空间下,这个类能够运行一些工作,当工作完成时,触发通告(notification)事件。这对于图形界面编程非常有用,因为完成的通告由图形界面线程触发,有助于强制执行图形界面对象只应该由创建它的线程进行更改的规则。

特别地,这个示例创建了计算斐波纳契数列,使用的是第们在第七章介绍的斐波纳契算法:

module Strangelights.Extensions

let fibs =

(0I,1I)|> Seq.unfold

(fun(n0, n1) ->

Some(n0, (n1, n0 + n1)))

let fib n = Seq.nth n fibs

[

起始值应该是(0, 1)

]

为这个文教二个图形界面也很简单,可以使用我们在第八间介绍的 Windows 窗体图形界面工具:

open Strangelights.Extensions

open System

open System.Windows.Forms

let form =

letform = new Form()

//input text box

letinput = new TextBox()

//button to launch processing

letbutton = new Button(Left = input.Right + 10, Text = "Go")

//label to display the result

letoutput = new Label(Top = input.Bottom + 10, Width = form.Width,

Height = form.Height -input.Bottom + 10,

Anchor = (AnchorStyles.Top||| AnchorStyles.Left |||

AnchorStyles.Right||| AnchorStyles.Bottom))

//do all the work when the button is clicked

button.Click.Add(fun_ ->

output.Text<- Printf.sprintf "%A" (fib (Int32.Parse(input.Text))))

//add the controls

letdc c = c :> Control

form.Controls.AddRange([|dcinput; dc button; dc output |])

//return the form

form

// show the form

do Application.Run(form)

运行后创建的圇界面如图 10-1:

图 10-1 计算斐波纳契数列的图形界面

这个图形界面以合理的方式显示计算结果,但是很不幸,一旦计算时间变长,图形界面就变得没有反映了。下面的代码就是造成不反映的原因:

// do all the work when the button isclicked

button.Click.Add(fun _ ->

output.Text<- Printf.sprintf "%A" (fib (Int32.Parse(input.Text))))

这段代码表示我们做的所有计算与触发单击事件是在同一个线程中,即图形界面线程,就是说,图形界面线程是负责计算的,在执行计算期间,就不可能处理其他事件。

把它改成使用后台辅助线程是相当容易:

open Strangelights.Extensions

open System

open System.ComponentModel

open System.Windows.Forms

let form =

letform = new Form()

//input text box

letinput = new TextBox()

//button to launch processing

letbutton = new Button(Left = input.Right + 10, Text = "Go")

//label to display the result

letoutput = new Label(Top = input.Bottom + 10, Width = form.Width,

Height = form.Height -input.Bottom + 10,

Anchor = (AnchorStyles.Top||| AnchorStyles.Left |||

AnchorStyles.Right||| AnchorStyles.Bottom))

//create and run a new background worker

letrunWorker() =

letbackground = new BackgroundWorker()

//parse the input to an int

letinput = Int32.Parse(input.Text)

//add the "work" event handler

background.DoWork.Add(funea ->

ea.Result<- fib input)

//add the work completed event handler

background.RunWorkerCompleted.Add(funea ->

output.Text <- Printf.sprintf"%A" ea.Result)

//start the worker off

background.RunWorkerAsync()

//hook up creating and running the worker to the button

button.Click.Add(fun_ -> runWorker())

//add the controls

letdc c = c :> Control

form.Controls.AddRange([|dcinput; dc button; dc output |])

//return the form

form

// show the form

do Application.Run(form)

使用后台辅助线程只要对代码做很少的修改,把代码分成DoWork 和 RunWorkerCompleted 事件,再稍许写一点代码,但除此之外,再不要求其他的代码了。我们就看看需要修改的代码,首选创建后台辅助线程类的实例:

let background = new BackgroundWorker()

把这个代码放在需要在后台运行的其他线程中,即在DoWork 事件中;还需要小心从DoWork 的控件外提取所有需要的数据;因为这个代码发生在不同的线程中,使代码与图形界面对象进行交互可能打破只由图形界面线程管理的规则。下面的代码用于读整数,并传给DoWork 事件:

// parse the input to an int

let input = Int32.Parse(input.Text)

// add the "work" event handler

background.DoWork.Add(fun ea ->

ea.Result<- fib input)

在前面的示例中,从文本框中提取整数,并刚好在把事件处理程序添加到DoWork 事件之前进行解析;接下来,添加到DoWork 事件中的lambda 函数捕捉到这个整数结果,应该把这个结果放在DoWork 事件中的Result 属性中,成为事件参数;然后,在RunWorkerCompleted 事件中恢复这个属性中的值。它们两个都有 Result属性,如下面代码所示:

// add the work completed event handler

background.RunWorkerCompleted.Add(fun ea->

output.Text<- Printf.sprintf "%A" ea.Result)

RunWorkerCompleted 事件当然可以运行在图形界面线程中,因此,很容易和图形界面对象进行交互。我们已经把事件都连接好了,但还余下两个任务:第一,需要启动后台辅助线程:

// start the worker off

background.RunWorkerAsync()

第二,需要把所有这些代码添加到按钮的单击事件中。我们已经把前面的代码包装到一个函数runWorker() 中,因此,在事件处理程序中调用这个代码就很简单了:

// hook up creating and running the workerto the button

button.Click.Add(fun _ -> runWorker())

注意,这表示每次单击按钮就创建一个新的后台辅助线程,这是因为后台辅助线程一旦使用,就不能重用。

现在,不管你单击多少次 Go 按钮,图形界面都能响应。但这也导致了其他问题,例如,很容易就能启动两次计算,这都是需要花费一些时间才能完成的。如果发生了这种情况,两次结果都会放在同一个结果标签中,这样,用户不可能知道哪一个是先完成的,当看到时,已经显示出来了。图形界面能保持响应,但并不能很好地适应多线程网格的编程,一种解决方案是在计算期间禁用所有控件,对某些情况,这可能是合适的,但是,整体来讲,这不是很好的解决方案,因为如果这样用户就不能很好地利用响应式图形界面的了。更好的解决方案是创建一个可显示多个结果的系统,对应其初始参数;这样,就能保证用户可以知道结果是什么含义。这个示例使用数据网格视图来显示结果:

open Strangelights.Extensions

open System

open System.ComponentModel

open System.Windows.Forms

open System.Numerics

// define a type to hold the results

type Result =

{Input: int;

Fibonacci:BigInteger; }

let form =

letform = new Form()

//input text box

letinput = new TextBox()

//button to launch processing

letbutton = new Button(Left = input.Right + 10, Text = "Go")

//list to hold the results

letresults = new BindingList<Result>()

//data grid view to display multiple results

letoutput = new DataGridView(Top = input.Bottom + 10, Width = form.Width,

Height =form.Height - input.Bottom + 10,

Anchor =(AnchorStyles.Top ||| AnchorStyles.Left |||

AnchorStyles.Right||| AnchorStyles.Bottom),

DataSource =results)

// create and run a new background worker

let runWorker() =

letbackground = new BackgroundWorker()

//parse the input to an int

letinput = Int32.Parse(input.Text)

//add the "work" event handler

background.DoWork.Add(funea ->

ea.Result<- (input, fib input))

//add the work completed event handler

background.RunWorkerCompleted.Add(funea ->

letinput, result = ea.Result :?> (int * BigInteger)

results.Add({Input = input; Fibonacci = result; }))

//start the worker off

background.RunWorkerAsync()

//hook up creating and running the worker to the button

button.Click.Add(fun_ -> runWorker())

//add the controls

letdc c = c :> Control

form.Controls.AddRange([|dcinput; dc button; dc output |])

//return the form

form

// show the form

do Application.Run(form)

新的图形界面如图 10-2 所示:

图 10-2 更好地适应多线程编程的图形界面

响应式编程(Reactive programming)

时间: 2024-10-24 02:41:43

响应式编程(Reactive programming)的相关文章

Unity基于响应式编程(Reactive programming)入门

系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现 时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画 时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率 背景 前有慕容小匹夫的一篇<解构C#游戏框架uFrame兼谈游戏架构设计&

Net中的反应式编程(Reactive Programming)

目录 系列主题:基于消息的软件架构模型演变 系列主题:基于消息的软件架构模型演变 一.反应式编程(Reactive Programming) 1.什么是反应式编程:反应式编程(Reactive programming)简称Rx,他是一个使用LINQ风格编写基于观察者模式的异步编程模型.简单点说Rx = Observables + LINQ + Schedulers. 2.为什么会产生这种风格的编程模型?我在本系列文章开始的时候说过一个使用事件的例子: 1 2 3 4 5 6 7 8 9 var 

IOS开发之OC篇-响应式编程Reactive Cocoa

一.Reactive Cocoa 介绍 Reactive Cocoa 是 iOS 开发的一个 "重量级" 框架 高大上的概念:响应式编程 核心概念:信号 Signal 官方网站:https://github.com/ReactiveCocoa/ReactiveCocoa 二.相关概念 1> 响应式编程 举个栗子,在一般程序开发时  a = b + c , 赋值之后 b 或者 c 的值变化后,a 的值不会跟着变化, 如果使用响应式编程,目标就是,如果 b 或者 c 的数值发生变化,

[译] Swift 的响应式编程

原文  https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-3/Swift的响应式编程.md 原文链接 : Reactive Swift 原文作者 : Agnes Vasarhelyi 译文出自 : 开发技术前线 www.devtf.cn 译者 :Mr.Simple 校对者:Lollypo 状态 : 完成 让我们首先回到Apple刚推出Objective-C的继任者-Swift的时候,那真是一个非比寻常的时刻. Sir

[转帖]浅谈响应式编程(Reactive Programming)

浅谈响应式编程(Reactive Programming) https://www.jianshu.com/p/1765f658200a 例子写的非常好呢. 0.9312018.02.14 21:22:16字数 1877阅读 9816 这是告别CSDN后第一次使用简书写IT类的博客,还在适应.最不适应的就是不能直接手输markdown语法标记.(好像原因是我没有切换编辑器) 什么是响应式编程(Reactive Programming) In computing, reactive program

响应式编程(Reactive Programming)(Rx)介绍

很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它们都流于表面,从没有围绕响应式编程构建起一个完整的知识体系.库的文档往往也无法帮助你去了解它的函数.不信的话可以看一下这个: 通过合并元素的指针,将每一个可观察的元素序列放射到一个新的可观察的序列中,然后将多个可观察的序列中的一个转换成一个只从最近的可观察序列中产生值得可观察的序列. 天啊. 我看过

函数式响应式编程 - Functional Reactive Programming

我们略过概念,直接看函数式响应式编程解决了什么问题. 故事从下面这个例子展开: 两个密码输入框,一个提交按钮. 密码.确认密码都填写并一致,允许提交:不一致提示错误. HTML 如下: <input id="pwd" placeholder="输入密码" type="password" /><br /> <input id="confirmPwd" placeholder="再次确认&q

FRP-Functional Reactive Programming-函数响应式编程

响应式编程是一种面向数据流和变化传播的编程范式: 响应式编程和函数式编程的融合: 响应式编程为内核:函数式编程为工具: 流的概念先天适合函数式编程. Some quotes from the article: Reactive programming is programming with asynchronous data streams. On top of that, you are given an amazing toolbox of functions to combine, cre

Reactive Cocoa 响应式编程开发实例讲解-中篇

上一篇文章作为开门篇讲述了Cocoa Reactive概述. 这里我们详细介绍一下CocoaReative在代码中的应用. 网上好多blog有人形容CocoaReative 中 signals是插座或者水龙头,感觉不是很好理解.我举个更贴近生活的,用电话订菜(餐馆是Signals,电话订阅是SubScriberNext). 1.概述 Create一个Signal我们视为是一个支持电话订餐的餐馆,他们有很多菜,油盐酱醋就更不用说,当一个电话打进来首先,这个Signal就开始执行,等菜做好了,菜馆要