性能优化总结(四):预加载的设计
本节说一下数据的预加载。这节的内容与SQL没什么关系。主要说的是在 GIX4项目 中,我们是如何设计符合需求的预加载类库的。内容如下:
- 什么是预加载,为什么要用它?
- 我们所需要的API
- 一个简单的例子
什么是预加载?
预加载其实就是在真正开始使用数据之前,先异步把数据加载好,等到需要使用时,就可以直接使用之前加载好的数据。这时,由于数据已经加载完成,而不用等待漫长的加载过程,所以程序的速度得到一个明显的提升。
那么,什么时候需要使用它呢?我觉得,主要是这种情况:当我们可以预知程序接下来的步骤中,很可能会用到一些数据,而获取这些数据的操作比较耗时的时候,我们就可以使用预加载的方式,提前把数据准备好。
预加载需要使用异步方法,也就是使用后台线程来加载数据。这样做的好处当然是不会阻塞当前的主线程。(不过如果当前线程本身就是用于异步加载数据的话,那就没必要再新开线程了。)
我们可以使用很多种方式来实现异步加载:在.NET Framework的类库中,很多地方都提供了异步编程模式(Asynchronous Programming Design Patterns)的API,使用这个模式,可以方便地实现各种异步加载。当然,我们也可以使用2.0提供的ThreadPool.QueueUserWorkItem来实现一些轻量级的异步操作。在.NET4.0最新的API中,提供了Task类来表示可执行任务。
但是,这些并不是我想要的API……
我们所需要的API
目前系统中预加载使用的场景需求是这样的:
- 预加载可以对指定的数据获取操作(loading action)进行封装,在需要时调用。
- 使用数据的模块(使用者),并不一定知道是谁、在何时给它提前加载的数据。它只会申请使用数据。
- 发起异步加载的模块(发起者),应该知道使用者是谁。
- 多个发起者之间没有关系,但是都可以为某一使用者发起预加载。但是保证真正的数据加载操作,只会发生一次。
- 支持重新加载。
- 一个类中,支持对它不同的数据进行不同加载方式,以方便按需加载。
- 从使用者的角度来看,不管有没有发起者为它进行预加载,它都可以申请并拿到想要的数据。也就是说: 当没有发起者为它进行预加载,那么它的数据申请会导致即时的数据加载; 如果已经发起了预加载,而且数据已经加载完成,则直接获取到加载好的数据; 如果数据没有完成,则数据使用者需要等待数据的加载完成后,才可以获取到数据并继续当前的操作。
其中,最重要的就是最后点。
可以看到,这里需要用到异步操作、线程间同步。所以我们需要基于上面提到的多种API来实现,这里我们使用的是简单的线程池的方式,比较简单,不再赘述。
最后设计出的API大致是这样的:
namespace OpenExpressApp
{
public enum LoaderStatus
{
NotStarted = 0,
Running = 1,
Completed = 2,
Failed = 3,
}
public class ForeAsyncLoader
{
public ForeAsyncLoader(Action loadAction);
public LoaderStatus Status { get; }
public event EventHandler ActionSucceeded;
/// <summary>
/// 申请启用线程进行预加载。
/// 注意:
/// 本方法可以重入,多次调用也只会执行一次ladAction
/// </summary>
public void BeginLoading();
/// <summary>
/// 重设加载器。
/// 使用此方法后,再次申请预加载时,会再次执行loadAction。
/// </summary>
public void Reset();
/// <summary>
/// 等待数据加载完成。
/// </summary>
public void WaitForLoading();
}
}
例子
客户程序使用时,需要为其定义一个属性,举例如下:
数据持有者:
public class DataHolder
{
private object _data;
public object Data
{
get
{
return this._data;
}
}
private object GetDataFromWeb()
{
//...
}
}
如果它的data1数据加载比较慢,我们可以为其定义一个预加载属性:
private ForeAsyncLoader _dataLoader;
/// <summary>
/// 数据加载器
/// </summary>
public ForeAsyncLoader DataLoader
{
get
{
if (this._dataLoader == null)
{
this._dataLoader = new ForeAsyncLoader(() =>
{
//真正加载数据
this._data = this.GetDataFromWeb();
});
}
return this._dataLoader;
}
}
这样,数据的“消费者”就可以使用这个数据:
public class DataConsumer
{
private void Process(DataHolder holder)
{
holder.DataLoader.WaitForLoading();
var data = holder.Data;
//consume data...
}
}
在这里,虽然使用者并不知道有没有其它代码给holder执行了数据的预加载,但是当WaitForLoading方法执行完成后,数据是必然获取到本地了。所以就可以直接使用数据。我们甚至可以把这句代码放在Data属性的get代码块中,这样,使用者甚至都不知道数据的获取方案!
然后,可以在运行于它之前的代码中,为这个“DataHolder”申请预加载。例如,我们在应用程序启动的时候,就开始预加载。下面的方法调用了BeginLoading方法,此方法会使用后台线程加载数据,所以这里会立即返回:
public class Invoker
{
private DataHolder _holder = new DataHolder();
private void App_Start()
{
this._holder.DataLoader.BeginLoading();
//do other things
}
}
至此,就完成了一个最简单的预加载。
过程如下:
(图画得不熟,哪画错了,望大家指正,谢谢。)
小结
本篇主要说了一下在目前的系统中,如何设计出一个满足场景应用需求的预加载API。
预加载是一个经常会被使用到的模式,希望对大家有用。
下一篇我会写一下与目前系统关联比较大的内容:与GIX4对象模型相关的“预加载、延迟加载、聚合SQL的组合应用”;另外可能会顺便说一下,如何让CSLA的服务端框架支持多线程并发。
- Discuz!X ≤3.4 任意文件删除漏洞分析
- 从学习 Paddle 开始学习深度学习
- 使用ffpython嵌入和扩展python
- FFLIB之FFLUA——C++嵌入Lua&扩展Lua利器
- Python之匿名函数
- H2Engine游戏服务器设计之属性管理器
- linux epoll 开发指南-【ffrpc源码解析】
- Python之递归函数
- 你不得不会的MarkDown
- 状态机的实现探讨
- Docker入门实战(二)——Docker镜像操作
- 使用强大的 Mockito 来测试你的代码
- java学习手册-CentOS 6.3(x86_32)下安装Oracle 10g R2
- Docker入门实战(三)——用Dockerfile构建镜像
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【tensorflow2.0】处理文本数据-imdb数据
- springmvc之异常处理DefaultHandlerExceptionResolver
- springmvc之返回json类型的数据给前端
- springmvc之mvc:view-controller标签设置可以直接访问的视图
- exec族
- springmvc实例之添加雇员相关信息(二)
- Qt引入第三方库(亲测Qt4和Qt5)
- springmvc之与spring进行整合
- recv&send函数
- setsockopt与getsockopt
- django-模型之(ORM)对象关系映射(一)
- Mybatis学习笔记(二)Mapper的配置问题
- 服务端向客户端传输文件
- 【tensorflow2.0】处理时间序列数据
- Socket网络通信之发送数据包