layui框架详细分析系列之熟悉框架以及提供的页面元素 - 玉案轩窗的博客 - CSDN博客

mikel阅读(863)

来源: layui框架详细分析系列之熟悉框架以及提供的页面元素 – 玉案轩窗的博客 – CSDN博客

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/s1879046/article/details/76095467
前不久,阅读了layer.js的源码,实际上它是layui框架的内置模块,不过可以独立出去使用,主要是用于弹出层的,layui框架中内置了很多模块,在未来的一段时间内,我都会对于这个框架的各个内置模块代码以及整个逻辑进行详细的描述和说明,以此来提高自己的各个方面的能力。

layui是一个UI框架,它的官网对其的说明是经典模块化前端框架,该框架的内部提供了一些UI框架常有的功能点,例如按钮、表单等。

先说说为什么选择这个框架作为学习源码过程中的第一步,原因大概有如下几点:

该框架不依赖与其他的Js框架,虽然它有的模块需要JQuery
它是基于原生Js的,对于我来说我不需要去了解其他Js的框架或库,减少分析该框架的成本
就正式进入正题吧,看框架源码是要有目的性的,这句话确实不错啊。如果只是为看而看,当你看到一定程度后,你就不想看了,代码是枯燥的,特别是看别人的源码,有了目的性,就有所不同,你知道自己想要什么。
在正式进入框架代码阅读分析之前,我认为需要熟悉和使用该框架,这样你在分析其源码的过程中,会更加清晰具体的功能。下面主要介绍该框架中命名规范以及提供的页面元素。
该框架中关于css类的命名规范分为两种,一种是具体模块的css的命令,它遵循layui-模块-状态或类型,另一种是公共类(可以说是不具体属于哪一个模块的),它遵循layui-状态或类型。
该框架中关于js的命名规则:
变量基本采用小写
方法名采用驼峰法命名
逻辑相关的都以is开头
获取相关的方法是以get开头
文件名小写
该框架提供的页面元素有:
内置几种背景颜色,主要的css类:.layui-bg-red、.layui-bd-orange等
图标,使用的是iconfont
按钮以及按钮组,分为大小、状态等,主要的css类:.layui-btn、.layui-btn-primary、.layui-btn-big等
表单,主要的css类有:.layui-form等
导航,分为水平导航、垂直导航、侧边栏导航,主要css类有:.layui-nav、.layui-nav-tree、.layui-nav-side等
选项卡,分为卡片选项卡、简洁风格、可删除的,主要css类有:.layui-tab等
表格,主要css类有.layui-table等
进度条,主要css类有.layui-progress、.layui-progress-bar等
面板,手风琴风格,主要的css类有.layui-collapse等
上面提供的都是css类,用于呈现该框架UI风格,一些功能需要交互才可以完成,所以需要引入模块来保证完整功能的使用。
本文主要是描述下该框架的css命名规范以及熟悉该框架,不出意外,明天会分析该框架中自己提供的模块加载机制,该收拾下班了。
致远行的你我
————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76095467

layui框架详细分析系列之框架主体组织结构 - 玉案轩窗的博客 - CSDN博客

mikel阅读(846)

来源: layui框架详细分析系列之框架主体组织结构 – 玉案轩窗的博客 – CSDN博客

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/s1879046/article/details/76162161
layui框架主体
今天正式的进入框架主体部分的学习与分析,该框架开源从GitHub上clone下来的源码主要的部分就是src部分,该部分主要的目录结构构成如下:

从上图可以看出css存储样式,font存储图标(iconfont), images存储图片,lay存储其他内置模块的js文件,layui.js文件就是主体部分。
layui.js还是采用IIFE的形式构成,如下面所示:

;!function(window, undefined) {
// 主要代码
}(window);
1
2
3
其中;号与!号的作用就不在啰嗦了,我之前的文章有对其进行解释,我分析了layui.js整体,绘制了思维导图,具体如下图所示:

从上图可以看出,主体文件的组织结构很清晰明了,主要分为:
1、需要用到的变量,比如内置的模块对象、获取layui.js文件路径的函数以及非常重要的config配置参数对象
2、Lay构造函数,面向对象编程,方法都定义在构造函数的原型对象上
3、通过Lay构造函数创建对象,并通过window对象将其暴露出去

主体结构清晰简洁,封装性感觉很好,没有不必要的属性暴露出去,框架提供的页面元素的相关都是通过定义的CSS类来实现,行为和样式分离开来,耦合性相对较小。

该框架实现了自己的模块加载机制以及自定义事件机制,实现方法也是定义在Lay的原型对象上,我是通过分析内置模块来分析相关联的主体函数的功能的,理解并给予详细的注释。

本篇文章主要分析主体文件的组织结构,相关注释的主体文件以及各个内置模块的注释源文件都会上传到我的Github上,下一篇将会分析该框架的模块加载机制。
远行的你我
————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76162161

layui源码详细分析系列之element模块以及自定义事件机制 - 玉案轩窗的博客 - CSDN博客

mikel阅读(830)

来源: layui源码详细分析系列之element模块以及自定义事件机制 – 玉案轩窗的博客 – CSDN博客

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/s1879046/article/details/76216485
element内置模块时layui框架中页面元素的交互功能的实现,主要是选项卡、面板、导航等的交互的实现。

下面先分析element模块的组织结构,具体如下图所示:

从上图中可以看出,element模块使用该框架自己的模块加载机制来加载该模块。

该框架内置的模块都是采用面向对象的编程,该模块也是,定义了Element构造函数,对外的API接口都定义在Element的原型对象上,使用JQuery来绑定一些原生的事件。call对象中定义真正处理选项卡等交互以及初始状态的处理程序。

element模块中使用框架自己定义的事件处理机制,具体实际上使用layui主体文件中的onevent和event来进行事件的注册以及事件的执行。

因为该模块最后输出的是函数类型,所以在使用该模块时要求如下:

layui.use([‘element’], function() {
var element = layui.element();
});
1
2
3
上面实际会自动执行一些初始化的工作,具体就会选项卡、面板等的初始状态的设置。

下面讲解layui自定义的事件机制,该框架定义事件名的形式如下:

功能名(lay-filter属性名)
1
在该框架中有lay-filter属性,该属性就是用于事件标识的。

什么时候使用该框架内置的事件机制?当你想要执行其他的操作,例如获取相关数据等,就可以使用自定义的事件机制。

下面使用实例来讲解框架事件机制的具体的逻辑处理,假设选项卡有属性lay-filter=’demo’, 那么就可以使用该框架自定义的事件机制,具体如下:

使用该机制的代码:
layui.use([‘element’], function() {
var ele = layui.element();

// tab(demo)就是事件名
ele.on(‘tab(demo)’, function(data) {
cosnole.log(data);
});
});
1
2
3
4
5
6
7
8
9
具体的逻辑流程如下图:

具体如上图所示,实际上内部维持了config.event对象来保存事件,onevent实现事件注册以及监听,实际上就是存储在config.event对象中,具体存储形式如下:

config.event[modeName + ‘.’ + event] = [callback]
1
在本例中modeName为element,event为tab(demo), callback就是就是事件处理程序,本例中callback如下:

callback = functio(data) {
console.log(data);
};
1
2
3
详细的代码注释以及相关的逻辑图我会上传到我的Github上,通过今天对于现在这种代码学习,有些迷茫了。

总结下现在这种阅读代码的方式的缺点:

有点纸上谈兵的感觉,不充实很虚
有些为看而看的感觉,没有非常大的收获,反而有些疲惫,没有自己成长了的自豪感
所以以后的文章就不会是这种方式出现了,目前想到的很好提高自己同时最大程度的提高自己,以后的风格将会以事实为依据,来具体展开,具体的形式如下:

自己会动手编写一个简易版实现核心功能的demo,并给出自己的思路
查看框架作者实现的思路,比较自己的思路与作者思路的相同点以及不同点,进行分析总结,丰满自己的思路
今天就到此为止,走走停停,方能走得更远,明天会分析文件上传以及流加载模块。
致远行的你我
————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76216485

git将本地项目托管到码云上(含git更换远程仓库步骤) - zhangzeshan的博客 - CSDN博客

mikel阅读(871)

来源: git将本地项目托管到码云上(含git更换远程仓库步骤) – zhangzeshan的博客 – CSDN博客

首先在码云上创建一个空项目

然后将自己本地的项目换成一个仓库,执行如下命令:(cd到自己的项目下)

git init
执行完毕后,本地项目就会生成.git的文件

下一步,添加远程仓库,也就是在码云上创建的空项目的链接

git remote add origin 码云项目的链接
下一步,将码云上的仓库pull到本地文件夹

git pull origin master
下一步,将自己的改动的数据进行提交:

git add .
然后添加提交的详细描述

git commit -m ‘新添加的文件内容描述’
然后推送到远程仓库:

git push origin master
如果这一步执行后,出现错误

那就执行一句命令:

git push -u origin master -f
就能完成托管了

 

下面说下更换代码仓库地址的具体步骤:

如果我刚刚添加的远程仓库不要了,需要更换到其他的仓库去,

首先,先把自己的原来的远程仓库删除:

git remote rm origin
然后,添加现在新的远程仓库地址

git remote add origin 新仓库地址
接下里,进行提交,测试看看自己是否更改成功:

git add .
git commit -m”#zhangzeshan#up”
git pull origin master
git push origin master
按照步骤,执行后完成后,就可以到码云上是否有自己提交的记录了

如果哪里说错,欢迎指出!
————————————————
版权声明:本文为CSDN博主「张芝山」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhangzeshan/article/details/82986434

VS2017中使用码云上传项目以及问题汇总 - 马儿不吃草 - CSDN博客

mikel阅读(1098)

来源: VS2017中使用码云上传项目以及问题汇总 – 马儿不吃草 – CSDN博客

方法一:个人项目和企业项目都可用此方法上传项目
1.下载码云在VS中的插件:下载地址

2.下载之后重启VS,然后打开团队资源管理器,也可以点击“团队”—>“管理连接”,在如下界面中点击连接,输入码云的账号密码

3.在码云的网站中进行新建项目(如果是公司项目归属选择公司即可)

4.项目新建完后在vs中点击克隆,选择刚刚新建的项目,然后选择项目的本地保存地址,点击克隆,克隆以后本地GIT存储库也会复制一份

5.然后将做好的项目拷贝到刚刚新建的目录下,如果是新项目,直接在该目录下进行新建项目即可

6.在本地GIT存储库中双击该项目,跳到该界面

7.如果是新项目第一次上传,点击更改,然后填写信息,点击全部提交并同步即可。在网站上就可以看到自己上传的项目啦,如果点击的只是全部提交,那么在提交后需要在同步中点一下同步按钮

8.如果是修改后项目要上传同步,点击更改,然后点击操作,修改上一个提交,然后再点击同步。一定要点击同步,不同步的话代码不会上传到云上面

方法二:上传个人项目比较推荐,不建议企业项目使用该方式上传
1.在VS中的Gitee中点击创建仓库,选择好必要的信息和路径,点击新建

2.然后和方法一一样将项目源码拷贝到刚才新建的目录中,之后的操作就和方法一一样了

利用这种方式的话,项目默认是为开源的,转移到企业里需要企业转移码。

 

注意点:
1.上传的单个文件不能大于100M

2.不要上传bin目录和obj目录下的文件,上传时将需要上传的文件加到暂存中,然后再点操作,提交上一个修改,同步,暂存中的文件就会上传上去,更改数中的文件就不会上传
————————————————
版权声明:本文为CSDN博主「马儿不吃草」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/horseroll/article/details/82499556

Newtonsoft.Json高级用法 - 焰尾迭 - 博客园

mikel阅读(912)

来源: Newtonsoft.Json高级用法 – 焰尾迭 – 博客园

手机端应用讲究速度快,体验好。刚好手头上的一个项目服务端接口有性能问题,需要进行优化。在接口多次修改中,实体添加了很多字段用于中间计算或者存储,然后最终用Newtonsoft.Json进行序列化返回数据,经过分析一个简单的列表接口每一行数据返回了16个字段,但是手机APP端只用到了其中7个字段,剩余9个字段的数据全部都是多余的,如果接口返回数据为40K大小,也就是说大约20K的数据为无效数据,3G网络下20K下载差不多需要1s,不返回无效数据至少可以节约1s的时间,大大提高用户体验。本篇将为大家介绍Newtonsoft.Json的一些高级用法,可以修改很少的代码解决上述问题。

阅读目录

Newtonsoft.Json介绍

在做开发的时候,很多数据交换都是以json格式传输的。而使用Json的时候,我们很多时候会涉及到几个序列化对象的使用:DataContractJsonSerializer,JavaScriptSerializer  Json.NET即Newtonsoft.Json。大多数人都会选择性能以及通用性较好Json.NET,这个不是微软的类库,但是一个开源的世界级的Json操作类库,从下面的性能对比就可以看到它的其中之一的性能优点。

齐全的API介绍,使用方式简单

 

基本用法

Json.Net是支持序列化和反序列化DataTable,DataSet,Entity Framework和Entity的。下面分别举例说明序列化和反序列化。

DataTable:

复制代码
            //序列化DataTable
            DataTable dt = new DataTable();
            dt.Columns.Add("Age", Type.GetType("System.Int32"));
            dt.Columns.Add("Name", Type.GetType("System.String"));
            dt.Columns.Add("Sex", Type.GetType("System.String"));
            dt.Columns.Add("IsMarry", Type.GetType("System.Boolean"));
            for (int i = 0; i < 4; i++)
            {
                DataRow dr = dt.NewRow();
                dr["Age"] = i + 1;
                dr["Name"] = "Name" + i;
                dr["Sex"] = i % 2 == 0 ? "男" : "女";
                dr["IsMarry"] = i % 2 > 0 ? true : false;
                dt.Rows.Add(dr);
            }
            Console.WriteLine(JsonConvert.SerializeObject(dt));
复制代码

利用上面字符串进行反序列化

 string json = JsonConvert.SerializeObject(dt);
 dt=JsonConvert.DeserializeObject<DataTable>(json);
 foreach (DataRow dr in dt.Rows)
 {
   Console.WriteLine("{0}\t{1}\t{2}\t{3}\t", dr[0], dr[1], dr[2], dr[3]);
 }

Entity序列化和DataTable一样,就不过多介绍了。

高级用法

1.忽略某些属性

2.默认值的处理

3.空值的处理

4.支持非公共成员

5.日期处理

6.自定义序列化的字段名称

7.动态决定属性是否序列化

8.枚举值的自定义格式化问题

9.自定义类型转换

10.全局序列化设置

 一.忽略某些属性

类似本问开头介绍的接口优化,实体中有些属性不需要序列化返回,可以使用该特性。首先介绍Json.Net序列化的模式:OptOut 和 OptIn

OptOut 默认值,类中所有公有成员会被序列化,如果不想被序列化,可以用特性JsonIgnore
OptIn 默认情况下,所有的成员不会被序列化,类中的成员只有标有特性JsonProperty的才会被序列化,当类的成员很多,但客户端仅仅需要一部分数据时,很有用

 

 

仅需要姓名属性

复制代码
    [JsonObject(MemberSerialization.OptIn)]
    public class Person
    {
        public int Age { get; set; }

        [JsonProperty]
        public string Name { get; set; }

        public string Sex { get; set; }

        public bool IsMarry { get; set; }

        public DateTime Birthday { get; set; }
    }
复制代码

不需要是否结婚属性

复制代码
    [JsonObject(MemberSerialization.OptOut)]
    public class Person
    {
        public int Age { get; set; }

        public string Name { get; set; }

        public string Sex { get; set; }

        [JsonIgnore]
        public bool IsMarry { get; set; }

        public DateTime Birthday { get; set; }
    }
复制代码

通过上面的例子可以看到,要实现不返回某些属性的需求很简单。1.在实体类上加上[JsonObject(MemberSerialization.OptOut)] 2.在不需要返回的属性上加上 [JsonIgnore]说明。

二.默认值处理

序列化时想忽略默认值属性可以通过JsonSerializerSettings.DefaultValueHandling来确定,该值为枚举值

DefaultValueHandling.Ignore
序列化和反序列化时,忽略默认值
DefaultValueHandling.Include
序列化和反序列化时,包含默认值

 

 

 

 [DefaultValue(10)]
 public int Age { get; set; }
 Person p = new Person { Age = 10, Name = "张三丰", Sex = "男", IsMarry = false, Birthday = new DateTime(1991, 1, 2) };
 JsonSerializerSettings jsetting=new JsonSerializerSettings();
 jsetting.DefaultValueHandling=DefaultValueHandling.Ignore;
 Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

最终结果如下:

 

三.空值的处理

  序列化时需要忽略值为NULL的属性,可以通过JsonSerializerSettings.NullValueHandling来确定,另外通过JsonSerializerSettings设置属性是对序列化过程中所有属性生效的,想单独对某一个属性生效可以使用JsonProperty,下面将分别展示两个方式

1.JsonSerializerSettings

 Person p = new Person { room=null,Age = 10, Name = "张三丰", Sex = "男", IsMarry = false, Birthday = new DateTime(1991, 1, 2) };
 JsonSerializerSettings jsetting=new JsonSerializerSettings();
 jsetting.NullValueHandling = NullValueHandling.Ignore;
 Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

2.JsonProperty

通过JsonProperty属性设置的方法,可以实现某一属性特别处理的需求,如默认值处理,空值处理,自定义属性名处理,格式化处理。上面空值处理实现

 [JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
 public Room room { get; set; }

 

四.支持非公共成员

序列化时默认都是处理公共成员,如果需要处理非公共成员,就要在该成员上加特性”JsonProperty”

 [JsonProperty]
 private int Height { get; set; }

 

五.日期处理

  对于Dateime类型日期的格式化就比较麻烦了,系统自带的会格式化成iso日期标准,但是实际使用过程中大多数使用的可能是yyyy-MM-dd 或者yyyy-MM-dd HH:mm:ss两种格式的日期,解决办法是可以将DateTime类型改成string类型自己格式化好,然后在序列化。如果不想修改代码,可以采用下面方案实现。

Json.Net提供了IsoDateTimeConverter日期转换这个类,可以通过JsnConverter实现相应的日期转换

    [JsonConverter(typeof(IsoDateTimeConverter))]
    public DateTime Birthday { get; set; }

但是IsoDateTimeConverter日期格式不是我们想要的,我们可以继承该类实现自己的日期

复制代码
    public class ChinaDateTimeConverter : DateTimeConverterBase
    {
        private static IsoDateTimeConverter dtConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd" };

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return dtConverter.ReadJson(reader, objectType, existingValue, serializer);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            dtConverter.WriteJson(writer, value, serializer);
        }
    }
复制代码

自己实现了一个yyyy-MM-dd格式化转换类,可以看到只是初始化IsoDateTimeConverter时给的日期格式为yyyy-MM-dd即可,下面看下效果

[JsonConverter(typeof(ChinaDateTimeConverter))]
public DateTime Birthday { get; set; }

可以根据自己需求实现不同的转换类

 

六.自定义序列化的字段名称

    实体中定义的属性名可能不是自己想要的名称,但是又不能更改实体定义,这个时候可以自定义序列化字段名称。

     [JsonProperty(PropertyName = "CName")]
     public string Name { get; set; }

 

七.动态决定属性是否序列化

  这个是为了实现@米粒儿提的需求特别增加的,根据某些场景,可能A场景输出A,B,C三个属性,B场景输出E,F属性。虽然实际中不一定存在这种需求,但是json.net依然可以支持该特性。

继承默认的DefaultContractResolver类,传入需要输出的属性

重写修改了一下,大多数情况下应该是要排除的字段少于要保留的字段,  为了方便书写这里修改了构造函数加入retain表示props是需要保留的字段还是要排除的字段

复制代码
public class LimitPropsContractResolver : DefaultContractResolver
    {
        string[] props = null;

        bool retain;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="props">传入的属性数组</param>
        /// <param name="retain">true:表示props是需要保留的字段  false:表示props是要排除的字段</param>
        public LimitPropsContractResolver(string[] props, bool retain=true)
        {
            //指定要序列化属性的清单
            this.props = props;

            this.retain = retain;
        }

        protected override IList<JsonProperty> CreateProperties(Type type,

        MemberSerialization memberSerialization)
        {
            IList<JsonProperty> list =
            base.CreateProperties(type, memberSerialization);
            //只保留清单有列出的属性
            return list.Where(p => {
                if (retain)
                {
                    return props.Contains(p.PropertyName);
                }
                else
                {
                    return !props.Contains(p.PropertyName);
                }      
            }).ToList();
        }
复制代码
        public int Age { get; set; }

        [JsonIgnore]
        public bool IsMarry { get; set; }

        public string Sex { get; set; }
      JsonSerializerSettings jsetting=new JsonSerializerSettings();
      jsetting.ContractResolver = new LimitPropsContractResolver(new string[] { "Age", "IsMarry" });
      Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));

使用自定义的解析类,只输出”Age”, “IsMarry”两个属性,看下最终结果.只输出了Age属性,为什么IsMarry属性没有输出呢,因为标注了JsonIgnore

看到上面的结果想要实现pc端序列化一部分,手机端序列化另一部分就很简单了吧,我们改下代码实现一下

复制代码
  string[] propNames = null;
  if (p.Age > 10)
  {
    propNames = new string[] { "Age", "IsMarry" };
  }
  else
  {
      propNames = new string[] { "Age", "Sex" };
  }
  jsetting.ContractResolver = new LimitPropsContractResolver(propNames);
  Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented, jsetting));
复制代码

 

八.枚举值的自定义格式化问题

  默认情况下对于实体里面的枚举类型系统是格式化成改枚举对应的整型数值,那如果需要格式化成枚举对应的字符怎么处理呢?Newtonsoft.Json也帮我们想到了这点,下面看实例

复制代码
    public enum NotifyType
    {
        /// <summary>
        /// Emil发送
        /// </summary>
        Mail=0,

        /// <summary>
        /// 短信发送
        /// </summary>
        SMS=1
    }

    public class TestEnmu
    {
        /// <summary>
        /// 消息发送类型
        /// </summary>
        public NotifyType Type { get; set; }
    }
    JsonConvert.SerializeObject(new TestEnmu());
复制代码

输出结果:  现在改造一下,输出”Type”:”Mail”

复制代码
    public class TestEnmu
    {
        /// <summary>
        /// 消息发送类型
        /// </summary>
        [JsonConverter(typeof(StringEnumConverter))]
        public NotifyType Type { get; set; }
    }
复制代码

其它的都不变,在Type属性上加上了JsonConverter(typeof(StringEnumConverter))表示将枚举值转换成对应的字符串,而StringEnumConverter是Newtonsoft.Json内置的转换类型,最终输出结果

 

九.自定义类型转换

默认情况下对于实体里面的Boolean系统是格式化成true或者false,对于true转成”是” false转成”否”这种需求改怎么实现了?我们可以自定义类型转换实现该需求,下面看实例

复制代码
public class BoolConvert : JsonConverter
    {
        private string[] arrBString { get; set; }

        public BoolConvert()
        {
            arrBString = "是,否".Split(',');
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="BooleanString">将bool值转换成的字符串值</param>
        public BoolConvert(string BooleanString)
        {
            if (string.IsNullOrEmpty(BooleanString))
            {
                throw new ArgumentNullException();
            }
            arrBString = BooleanString.Split(',');
            if (arrBString.Length != 2)
            {
                throw new ArgumentException("BooleanString格式不符合规定");
            }
        }


        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            bool isNullable = IsNullableType(objectType);
            Type t = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;

            if (reader.TokenType == JsonToken.Null)
            {
                if (!IsNullableType(objectType))
                {
                    throw new Exception(string.Format("不能转换null value to {0}.", objectType));
                }

                return null;
            }

            try
            {
                if (reader.TokenType == JsonToken.String)
                {
                    string boolText = reader.Value.ToString();
                    if (boolText.Equals(arrBString[0], StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                    else if (boolText.Equals(arrBString[1], StringComparison.OrdinalIgnoreCase))
                    {
                        return false;
                    }
                }

                if (reader.TokenType == JsonToken.Integer)
                {
                    //数值
                    return Convert.ToInt32(reader.Value) == 1;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Error converting value {0} to type '{1}'", reader.Value, objectType));
            }
            throw new Exception(string.Format("Unexpected token {0} when parsing enum", reader.TokenType));
        }

        /// <summary>
        /// 判断是否为Bool类型
        /// </summary>
        /// <param name="objectType">类型</param>
        /// <returns>为bool类型则可以进行转换</returns>
        public override bool CanConvert(Type objectType)
        {
            return true;
        }


        public bool IsNullableType(Type t)
        {
            if (t == null)
            {
                throw new ArgumentNullException("t");
            }
            return (t.BaseType.FullName=="System.ValueType" && t.GetGenericTypeDefinition() == typeof(Nullable<>));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }

            bool bValue = (bool)value;

            if (bValue)
            {
                writer.WriteValue(arrBString[0]);
            }
            else
            {
                writer.WriteValue(arrBString[1]);
            }
        }
    }
复制代码

自定义了BoolConvert类型,继承自JsonConverter。构造函数参数BooleanString可以让我们自定义将true false转换成相应字符串。下面看实体里面怎么使用这个自定义转换类型

    public class Person
    {
        [JsonConverter(typeof(BoolConvert))]
        public bool IsMarry { get; set; }
    }

相应的有什么个性化的转换需求,都可以使用自定义转换类型的方式实现。

 

十.全局序列化设置

文章开头提出了Null值字段怎么不返回的问题,相应的在高级用法也给出了相应的解决方案使用jsetting.NullValueHandling = NullValueHandling.Ignore; 来设置不返回空值。这样有个麻烦的地方,每个不想返回空值的序列化都需设置一下。可以对序列化设置一些默认值方式么?下面将解答

复制代码
  Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
   JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
   {
    //日期类型默认格式化处理
     setting.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
      setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";

    //空值处理
      setting.NullValueHandling = NullValueHandling.Ignore;

      //高级用法九中的Bool类型转换 设置
      setting.Converters.Add(new BoolConvert("是,否"));

      return setting;
   });
复制代码

 

这样设置以后,以后使用序列化的地方就不需要单独设置了,个人最喜欢设置的是空值处理这一块。

总结

Newtonsoft.Json序列化库替我们想了很多特性,也实现了很多特性,除了上面介绍的几种高级用法外,还有其它的特殊用法,可以去官网进行学习。当然这里我目前最喜欢的特性就是那个忽略部分属性序列化的功能,很小的代码改动实现了接口的优化,提升了用户体验。

 

string.Format出现异常:输入字符串的格式不正确 Exception during StringFormat - jhlong - 博客园

mikel阅读(1529)

来源: string.Format出现异常:输入字符串的格式不正确 Exception during StringFormat – jhlong – 博客园

错误信息:Exception during StringFormat:输入字符串的格式不正确

“System.FormatException”类型的未经处理的异常在 mscorlib.dll 中发生

其他信息: 输入字符串的格式不正确。

string s = string.Format("{return:\"{0}\"}", result);//这里报错

后来发现是 占位符 result 里含有 “{}” 大括号导致。

解决办法:替换处理result,

用两个 { 或者 }连写表示单个。

或者其他办法

字符串的{改成{{和}改成}}

String code = “{{queryParam:{{\”Warehouse\”: [{{\”Code\”: \”\”}}],\”TimeBegin\”: \”{0}\”,\”TimeEnd\”: \”{1}\”}}”;

try
{
String d = DateTime.Now.AddMinutes(-300).ToString();
string dd = DateTime.Now.ToString();
code = String.Format(code,d,dd);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}

Visual Studio 2019 的安装 (附专业版和企业版产品密钥) - ZY-JIMMY - CSDN博客

mikel阅读(2407)

来源: Visual Studio 2019 的安装 (附专业版和企业版产品密钥) – ZY-JIMMY – CSDN博客

官网下载:
https://visualstudio.microsoft.com/zh-hans/

选择需要的功能即可开始安装:

安装成功后直接启动:

企业版安装后试用30天, 点击帮助 – 注册产品:

点击 使用产品密钥解锁

Visual Studio 2019 Enterprise Key:
BF8Y8-GN2QH-T84XB-QVY3B-RC4DF

Visual Studio 2019 Professional Key:
NYWVH-HT4XC-R2WYW-9Y3CM-X4V3Y

输入密钥,点击应用:

我们可以看到,产品已被解锁。

使用VS2019编写和调试Linux程序教程:
https://blog.csdn.net/ZYZMZM_/article/details/89044885
————————————————
版权声明:本文为CSDN博主「ZY-JIMMY」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZYZMZM_/article/details/89045810

C# Winform 跨线程更新UI控件常用方法汇总 - marshal-m - 博客园

mikel阅读(1109)

来源: C# Winform 跨线程更新UI控件常用方法汇总 – marshal-m – 博客园

概述

C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常。处理跨线程更新Winform UI控件常用的方法有4种:
1. 通过UI线程的SynchronizationContext的Post/Send方法更新;
2. 通过UI控件的Invoke/BeginInvoke方法更新;

3. 通过BackgroundWorker取代Thread执行异步操作;
4. 通过设置窗体属性,取消线程安全检查来避免”跨线程操作异常”(非线程安全,建议不使用)。
下文中对以上3种方法应用进行举例说明,希望能对初识C# Winform的同学们有些帮助。

成文表分享交流之意,惶恐水平有限,文中理解和表述有错误之处还请大家多被批评指正。

更新记录:

2018年2月3日,根据网友评论提示更新错别字,BegainInvoke=》BeginInvoke。

正文

1. 通过UI线程的SynchronizationContext的Post/Send方法更新

用法:

复制代码
        //共分三步
        //第一步:获取UI线程同步上下文(在窗体构造函数或FormLoad事件中)
        /// <summary>
        /// UI线程的同步上下文
        /// </summary>
        SynchronizationContext m_SyncContext = null;
        public Form1()
        {
            InitializeComponent();
            //获取UI线程同步上下文
            m_SyncContext = SynchronizationContext.Current;
            //Control.CheckForIllegalCrossThreadCalls = false;
        }
        //第二步:定义线程的主体方法
        /// <summary>
        /// 线程的主体方法
        /// </summary>
        private void ThreadProcSafePost()
        {
            //...执行线程任务

            //在线程中更新UI(通过UI线程同步上下文m_SyncContext)
            m_SyncContext.Post(SetTextSafePost, "This text was set safely by SynchronizationContext-Post.");

            //...执行线程其他任务
        }
        //第三步:定义更新UI控件的方法
        /// <summary>
        /// 更新文本框内容的方法
        /// </summary>
        /// <param name="text"></param>
        private void SetTextSafePost(object text)
        {
            this.textBox1.Text = text.ToString();
        }
        //之后,启动线程
        /// <summary>
        /// 启动线程按钮事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void setSafePostBtn_Click(object sender, EventArgs e)
        {
            this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafePost));
            this.demoThread.Start();
        }
复制代码

说明:三处加粗部分是关键。该方法的主要原理是:在线程执行过程中,需要更新到UI控件上的数据不再直接更新,而是通过UI线程上下文的Post/Send方法,将数据以异步/同步消息的形式发送到UI线程的消息队列;UI线程收到该消息后,根据消息是异步消息还是同步消息来决定通过异步/同步的方式调用SetTextSafePost方法直接更新自己的控件了。

在本质上,向UI线程发送的消息并是不简单数据,而是一条委托调用命令。

//在线程中更新UI(通过UI线程同步上下文m_SyncContext)
m_SyncContext.Post(SetTextSafePost, “This text was set safely by SynchronizationContext-Post.”);
可以这样解读这行代码:向UI线程的同步上下文(m_SyncContext)中提交一个异步消息(UI线程,你收到消息后以异步的方式执行委托,调用方法SetTextSafePost,参数是“this text was ….”).

 

2.通过UI控件的Invoke/BeginInvoke方法更新

用法:与方法1类似,可分为三个步骤。

复制代码
        // 共分三步

        // 第一步:定义委托类型
        // 将text更新的界面控件的委托类型
        delegate void SetTextCallback(string text);

        //第二步:定义线程的主体方法
        /// <summary>
        /// 线程的主体方法
        /// </summary>
        private void ThreadProcSafe()
        {
            //...执行线程任务

            //在线程中更新UI(通过控件的.Invoke方法)
            this.SetText("This text was set safely.");

            //...执行线程其他任务
        }
        //第三步:定义更新UI控件的方法
        /// <summary>
        /// 更新文本框内容的方法
        /// </summary>
        /// <param name="text"></param>
        private void SetText(string text)
        {
            // InvokeRequired required compares the thread ID of the 
            // calling thread to the thread ID of the creating thread. 
            // If these threads are different, it returns true. 
            if (this.textBox1.InvokeRequired)//如果调用控件的线程和创建创建控件的线程不是同一个则为True
            {
                while (!this.textBox1.IsHandleCreated)
                {
                    //解决窗体关闭时出现“访问已释放句柄“的异常
                    if (this.textBox1.Disposing || this.textBox1.IsDisposed)
                        return;
                }
                SetTextCallback d = new SetTextCallback(SetText);
                this.textBox1.Invoke(d, new object[] { text });
            }
            else
            {
                this.textBox1.Text = text;
            }
        }
        //之后,启动线程
        /// <summary>
        /// 启动线程按钮事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void setTextSafeBtn_Click(
            object sender,
            EventArgs e)
        {
            this.demoThread =
                new Thread(new ThreadStart(this.ThreadProcSafe));

            this.demoThread.Start();
        }
复制代码

说明:这个方法是目前跨线程更新UI使用的主流方法,使用控件的Invoke/BeginInvoke方法,将委托转到UI线程上调用,实现线程安全的更新。原理与方法1类似,本质上还是把线程中要提交的消息,通过控件句柄调用委托交到UI线程中去处理。

解决窗体关闭时出现“访问已释放句柄“的异常部分代码参考博客园-事理同学的文章

 

3.通过BackgroundWorker取代Thread执行异步操作

用法:

复制代码
        //共分三步

        //第一步:定义BackgroundWorker对象,并注册事件(执行线程主体、执行UI更新事件)
        private BackgroundWorker backgroundWorker1 =null;
        public Form1()
        {
            InitializeComponent();

           
            backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
            //设置报告进度更新
            backgroundWorker1.WorkerReportsProgress = true;
            //注册线程主体方法
            backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
            //注册更新UI方法
            backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
            //backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
        }

        //第二步:定义执行线程主体事件
        //线程主体方法
        public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            //...执行线程任务

            //在线程中更新UI(通过ReportProgress方法)
            backgroundWorker1.ReportProgress(50, "This text was set safely by BackgroundWorker.");

            //...执行线程其他任务
        }
        //第三步:定义执行UI更新事件
        //UI更新方法
        public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.textBox1.Text = e.UserState.ToString();
        }
        //之后,启动线程
        //启动backgroundWorker
        private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
        {
            this.backgroundWorker1.RunWorkerAsync();
        }
复制代码

说明:C# Winform中执行异步任务时,BackgroundWorker是个不错的选择。它是EAP(Event based Asynchronous Pattern)思想的产物,DoWork用来执行异步任务,在任务执行过程中/执行完成后,我们可以通过ProgressChanged,ProgressCompleteded事件进行线程安全的UI更新。

需要注意的是://设置报告进度更新
backgroundWorker1.WorkerReportsProgress = true;
默认情况下BackgroundWorker是不报告进度的,需要显示设置报告进度属性。

 

4. 通过设置窗体属性,取消线程安全检查来避免”线程间操作无效异常”(非线程安全,建议不使用)

用法:将Control类的静态属性CheckForIllegalCrossThreadCalls为false。

      public Form1()
        {
            InitializeComponent();
            //指定不再捕获对错误线程的调用
            Control.CheckForIllegalCrossThreadCalls = false;
        }

说明:通过设置CheckForIllegalCrossThreadCalls属性,可以指示是否捕获线程间非安全操作异常。该属性值默认为ture,即线程间非安全操作是要捕获异常的(”线程间操作无效”异常)。通过设置该属性为false简单的屏蔽了该异常。Control.CheckForIllegalCrossThreadCalls的注释如下

复制代码
        //
        // 摘要:
        //     获取或设置一个值,该值指示是否捕获对错误线程的调用,这些调用在调试应用程序时访问控件的 System.Windows.Forms.Control.Handle
        //     属性。
        //
        // 返回结果:
        //     如果捕获了对错误线程的调用,则为 true;否则为 false。
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [SRDescription("ControlCheckForIllegalCrossThreadCalls")]
        [Browsable(false)]
        public static bool CheckForIllegalCrossThreadCalls { get; set; }
复制代码

综述:

文中介绍的4种方法,前三种是线程安全的 ,可在实际项目中因地制宜的使用。最后一种方法是非线程安全的,初学者可以实验体会但不建议使用它。

下面列表对比一下这四种方法

方法 线程安全 支持异步/同步 其他
UI SyncContext更新 Post/Send 尽量在窗体构造函数、FormLoad中获取同步上下文
控件Invoke control.Invoke/BeginInvoke 注意检查控件句柄是否已释放
BackgroundWorker更新

ProgressChanged、RunWorkerCompleted

事件同步更新

报告进度
CheckForIllegalCrossThreadCalls

取消跨线程调用检查

同步更新 简单,不建议使用

 

 

 

 

 

参考资料:

瑞奇特(Jeffrey Richter),《clr var C#》

MSDN, How to: Make Thread-Safe Calls to Windows Forms Controls

C#的Timer - PowerCoder - 博客园

mikel阅读(839)

来源: C#的Timer – PowerCoder – 博客园

C#里现在有3个Timer类:

  • System.Windows.Forms.Timer
  • System.Threading.Timer
  • System.Timers.Timer

这三个Timer我想大家对System.Windows.Forms.Timer已经很熟悉了,唯一我要说的就是这个Timer在激发Timer.Tick事件的时候,事件的处理函数是在程序主线程上执行的,所以在WinForm上面用这个Timer很方便,因为在Form上的所有控件都是在程序主线程上创建的,那么在Tick的处理函数中可以对Form上的所有控件进行操作,不会造成WinForm控件的线程安全问题。

 

1、Timer运行的核心都是System.Threading.ThreadPool

 

在这里要提到ThreadPool(线程池)是因为,System.Threading.Timer 和System.Timers.Timer运行的核心都是线程池,Timer每到间隔时间后就会激发响应事件,因此要申请线程来执行对应的响应函数,Timer将获取线程的工作都交给了线程池来管理,每到一定的时间后它就去告诉线程池:“我现在激发了个事件要运行对应的响应函数,麻烦你给我向操作系统要个线程,申请交给你了,线程分配下来了你就运行我给你的响应函数,没分配下来先让响应函数在这儿排队(操作系统线程等待队列)”,消息已经传递给线程池了,Timer也就不管了,因为它还有其他的事要做(每隔一段时间它又要激发事件),至于提交的请求什么时候能够得到满足,要看线程池当前的状态:

  • 1、如果线程池现在有线程可用,那么申请马上就可以得到满足,有线程可用又可以分为两种情况:
    • <1>线程池现在有空闲线程,现在马上就可以用
    • <2>线程池本来现在没有线程了,但是刚好申请到达的时候,有线程运行完毕释放了,那么申请就可以用别人释放的线程。
    • 这两种情况就如同你去游乐园玩赛车,如果游乐园有10辆车,现在有3个人在玩,那么还剩7辆车,你去了当然可以选一辆开。另外还有一种情况就是你到达游乐园前10辆车都在开,但是你运气很好,刚到游乐园就有人不玩了,正好你坐上去就可以接着开。
  • 2、如果线程池现在没有线程可用,也分为两种情况:
    • <1>线程池现有线程数没有达到设置的最大工作线程数,那么隔半秒钟.net framework就会向操作系统申请一个新的线程(为避免向线程分配不必要的堆栈空间,线程池按照一定的时间间隔创建新的空闲线程。该时间间隔目前为半秒,但它在 .NET Framework 的以后版本中可能会更改)。
    • <2>线程池现有工作线程数达到了设置的最大工作线程数,那么申请只有在等待队列一直等下去,直到有线程执行完任务后被释放。

那么上面提到了线程池有最大工作线程数,其实还有最小空闲线程数,那么这两个关键字是什么意思呢:

  • 1、最大工作线程数:实际上就是指的线程池能够向操作系统申请的最大线程数,这个值在.net framework中有默认值,这个默认值是根据你计算机的配置来的,当然你可以用ThreadPool.GetMaxThreads返回线程池当前最大工作线程数,你也可以用ThreadPool.SetMaxThreads设置线程池当前最大工作线程数。
  • 2、最小空闲线程数:是指在程序开始后,线程池就默认向操作系统申请最小空闲线程数个线程,另外这也是线程池维护的空闲线程数(如果线程池最小空闲线程数为3,当前因为一些线程执行完任务被释放,线程池现在实际上有10个空闲线程,那么线程池会让操作系统释放多余的7个线程,而只维持3个空闲线程供程序使用),因为上面说了,在执行程序的时候再要求线程池申请线程会有半秒的延迟时间,这也会影响程序的性能,所以把握好这个值很重要,同样你可以用ThreadPool.GetMinThreads返回线程池当前最小空闲线程数,你也可以用ThreadPool.SetMinThreads设置线程池当前最小空闲线程数。

下面是我给的例子,这个例子让线程池申请800个线程,其中设置最大工作线程数为500,800个线程任务每个都要执行100000000毫秒目的是让线程不会释放,并且让用户选择,是否预先申请500个空闲线程免受那半秒钟的延迟时间,其结果可想而知当线程申请到500的时候,线程池达到了最大工作线程数,剩余的300个申请进入漫长的等待时间:

复制代码
/***************************************************
 * 项目:测试线程池
 * 描述:验证线程池的最大工作线程数和最小空闲线程数
 * 作者:@PowerCoder
 * 日期:2010-2-22
***************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static int i = 1;
        static int MaxThreadCount = 800;

        static void OutPut(object obj)
        {
            Console.Write("\r申请了:{0}个工作线程", i);
            i++;
            Thread.Sleep(100000000);//设置一个很大的等待时间,让每个申请的线程都一直执行
        }

        static void Main(string[] args)
        {
            int j;

            Console.Write("是否先申请500个空闲线程以保证前500个线程在线程池中开始就有线程用(Y/N)?");//如果这里选择N,那么前两个任务是用的线程池默认空闲线程(可以用ThreadPool.GetMinThreads得到系统默认最小空闲线程数为2,该值也和你计算机的配置有关,本例中ThreadPool.GetMinThreads返回的2)申请立即得到满足,然而由于每个线程等待时间非常大都不会释放当前自己持有的线程,因此线程池中已无空闲线程所用,后面的任务需要在线程池中申请新的线程,那么新申请的每个线程在线程池中都要隔半秒左右的时间才能得到申请(原因请见下面的注释)
            string key = Console.ReadLine();
            if (key.ToLower() == "y")
                ThreadPool.SetMinThreads(500, 10);//设置最小空闲线程为500,就好像我告诉系统给我预先准备500个线程我来了就直接用,因为这样就不用现去申请了,在线程池中每申请一个新的线程.NET Framework 会安排一个间隔时间,目前是半秒,以后的版本MS有可能会改

            ThreadPool.SetMaxThreads(500, 10);//设置最大工作线程为500

            int a, b;
            ThreadPool.GetMaxThreads(out a, out b);
            Console.WriteLine("线程池最大工作线程数:" + a.ToString() + "     最大异步 I/O 线程数:" + b.ToString());
            Console.WriteLine("需要向系统申请" + MaxThreadCount.ToString() + "个工作线程");

            for (j = 0; j <= MaxThreadCount - 1; j++)//由于ThreadPool.GetMaxThreads返回的最大工作线程数为500,那么向线程池申请大于500个线程的时候,500之后的申请会进入线程池的等待队列排队,等待前面500个线程中某个线程执行完后释放,线程池等待队列中的申请才能得到线程
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(OutPut));
                Thread.Sleep(10);
            }

            Console.ReadLine();
        }
    }
}
复制代码

 

 

2、System.Threading.Timer

谈完了线程池,就可以开始讨论Timer,这里我们先从System.Threading.Timer开始,System.Threading.Timer的作用就是每到间隔时间后激发响应事件并执行相应函数,执行响应函数要向线程池申请线程,当然申请中会遇到一些情况在上面我们已经说了。值得注意的一点就是System.Threading.Timer在创建对象后立即开始执行,比如System.Threading.Timer timer = new System.Threading.Timer(Excute, null, 0, 10);这句执行完后每隔10毫秒就执行Excute函数不需要启动什么的。下面就举个例子,我先把代码贴出来:

复制代码

代码

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication1
{
class UnSafeTimer
{
static int i = 0;
static System.Threading.Timer timer;
static object mylock = new object();
static int sleep;
static bool flag;
public static Stopwatch sw = new Stopwatch();

static void Excute(object obj)
{
Thread.CurrentThread.IsBackground = false;
int c;

lock (mylock)
{
i++;
c = i;
}

if (c == 80)
{
timer.Dispose();//执行Dispose后Timer就不会再申请新的线程了,但是还是会给Timmer已经激发的事件申请线程
sw.Stop();
}

if (c < 80)
Console.WriteLine(“Now:” + c.ToString());
else
{
Console.WriteLine(“Now:” + c.ToString()+”———–Timer已经Dispose耗时:”+sw.ElapsedMilliseconds.ToString()+”毫秒”);
}

if (flag)
{
Thread.Sleep(sleep);//模拟花时间的代码
}
else
{
if(i<=80)
Thread.Sleep(sleep);//前80次模拟花时间的代码
}
}

public static void Init(int p_sleep,bool p_flag)
{
sleep = p_sleep;
flag = p_flag;
timer = new System.Threading.Timer(Excute, null, 0, 10);
}
}

class SafeTimer
{
static int i = 0;
static System.Threading.Timer timer;

static bool flag = true;
static object mylock = new object();

static void Excute(object obj)
{
Thread.CurrentThread.IsBackground = false;

lock (mylock)
{
if (!flag)
{
return;
}

i++;

if (i == 80)
{
timer.Dispose();
flag = false;
}
Console.WriteLine(“Now:” + i.ToString());
}

Thread.Sleep(1000);//模拟花时间的代码
}

public static void Init()
{
timer = new System.Threading.Timer(Excute, null, 0, 10);
}
}

class Program
{
static void Main(string[] args)
{
Console.Write(“是否使用安全方法(Y/N)?”);
string key = Console.ReadLine();
if (key.ToLower() == “y”)
SafeTimer.Init();
else
{
Console.Write(“请输入Timmer响应事件的等待时间(毫秒):”);//这个时间直接决定了前80个任务的执行时间,因为等待时间越短,每个任务就可以越快执行完,那么80个任务中就有越多的任务可以用到前面任务执行完后释放掉的线程,也就有越多的任务不必去线程池申请新的线程避免多等待半秒钟的申请时间
string sleep = Console.ReadLine();
Console.Write(“申请了80个线程后Timer剩余激发的线程请求是否需要等待时间(Y/N)?”);//这里可以发现选Y或者N只要等待时间不变,最终Timer激发线程的次数都相近,说明Timer的确在执行80次的Dispose后就不再激发新的线程了
key = Console.ReadLine();
bool flag = false;
if (key.ToLower() == “y”)
{
flag = true;
}

UnSafeTimer.sw.Start();
UnSafeTimer.Init(Convert.ToInt32(sleep), flag);
}

Console.ReadLine();
}
}
}

复制代码

 

这个例子包含了两个Timer的类UnSafeTimer和SafeTimer,两个类的代码的大致意思就是使用Timer每隔10毫秒就执行Excute函数,Excute函数会显示当前执行的次数,在80次的时候通过timer.Dispose()让Timer停止不再激发响应事件。

首先我们来分析下UnSafeTimer

class UnSafeTimer
{
static int i = 0;
static System.Threading.Timer timer;
static object mylock = new object();
static int sleep;
static bool flag;
public static Stopwatch sw = new Stopwatch();

static void Excute(object obj)
{
Thread.CurrentThread.IsBackground = false;
int c;

lock (mylock)
{
i++;
c = i;
}

if (c == 80)
{
timer.Dispose();//执行Dispose后Timer就不会再申请新的线程了,但是还是会给Timmer已经激发的事件申请线程
sw.Stop();
}

if (c < 80)
Console.WriteLine(“Now:” + c.ToString());
else
{
Console.WriteLine(“Now:” + c.ToString() + “———–Timer已经Dispose耗时:” + sw.ElapsedMilliseconds.ToString() + “毫秒”);
}

if (flag)
{
Thread.Sleep(sleep);//模拟花时间的代码
}
else
{
if (i <= 80)
Thread.Sleep(sleep);//前80次模拟花时间的代码
}
}

public static void Init(int p_sleep, bool p_flag)
{
sleep = p_sleep;
flag = p_flag;
timer = new System.Threading.Timer(Excute, null, 0, 10);
}
}

你可以执行试一试,在输入是否执行安全方法的时候选N,等待时间1000,申请了80个线程后Timer剩余激发的线程选N,本来想在80次的时候停下来,可是你会发现直到执行到660多次之后才停下来(具体看机器配置),申请前80个线程的时间为10532毫秒,反正执行的次数大大超出了限制的80次,回头想想让Timer不在激发事件的方法是调用timer.Dispose(),难不成是Dispose有延迟?延迟的过程中多执行了500多次?那么我们再来做个试验,我们在申请了80个线程后Timer剩余激发的线程选y,请耐心等待结果,在最后你会发现执行时间还是660次左右,这很显然是不合理的,如果Dispose有延迟时间造成所执行500多次,那么加长80次后面每个线程的申请时间在相同的延迟时间内申请的线程数应该减少,因为后面500多个线程每个线程都要执行1000毫秒,那么势必有些线程会去申请新的线程有半秒钟的等待时间(你会发现申请了80个线程后Timer剩余激发的线程选y明显比选n慢得多,就是因为这个原因),所以看来不是因为Dispose造成的。

 

那么会是什么呢?我们这次这样选在输入是否执行安全方法的时候选N,等待时间500,申请了80个线程后Timer剩余激发的线程选N

 

那么会是什么呢?我们这次这样选在输入是否执行安全方法的时候选N,等待时间50,申请了80个线程后Timer剩余激发的线程选N

我们发现随着每次任务等待时间的减少多执行的次数也在减少,最关键的一点我们从图中可以看到,前80次任务申请的时间也在减少,这是最关键的,根据上面线程池所讲的内容我们可以归纳出:每次任务的等待时间直接决定了前80个任务的执行时间,因为等待时间越短,每个任务就可以越快执行完,那么80个任务中就有越多的任务可以用到前面任务执行完后释放掉的线程,也就有越多的任务不必去线程池申请新的线程避免多等待半秒钟的申请时间,而Timer并不会去关心线程池申请前80个任务的时间长短,只要它没有执行到timer.Dispose(),它就会每隔10毫秒激发一次响应时间,不管前80次任务执行时间是长还是短,timer都在第80次任务才执行Dispose,执行Dispose后timer就不会激发新的事件了,但是如果前80次任务申请的时间越长,那么timer就会在前80次任务申请的时间内激发越多响应事件,那么线程池中等待队列中就会有越多的响应函数等待申请线程,System.Threading.Timer没有机制取消线程池等待队列中多余的申请数,所以导致等待时间越长,80次后执行的任务数越多。

 

由此只用timer.Dispose()来终止Timer激发事件是不安全的,所以又写了个安全的执行机制:

 

class SafeTimer
{
static int i = 0;
static System.Threading.Timer timer;

static bool flag = true;
static object mylock = new object();

static void Excute(object obj)
{
Thread.CurrentThread.IsBackground = false;

lock (mylock)
{
if (!flag)
{
return;
}

i++;

if (i == 80)
{
timer.Dispose();
flag = false;
}
Console.WriteLine(“Now:” + i.ToString());
}

Thread.Sleep(1000);//模拟花时间的代码
}

public static void Init()
{
timer = new System.Threading.Timer(Excute, null, 0, 10);
}
}

安全类中我们用了个bool类型的变量flag来判断当前是否执行到80次了,执行到80次后将flag置为false,然后timer.Dispose,这时虽然任务还是要多执行很多次但是由于flag为false,Excute函数一开始就做了判断flag为false会立即退出,Excute函数80次后相当于就不执行了。

 

 

3、System.Timers.Timer

在上面的例子中我们看到System.Threading.Timer很不安全,即使在安全的方法类,也只能让事件响应函数在80次后立刻退出让其执行时间近似于0,但是还是浪费了系统不少的资源。

所以本人更推荐使用现在介绍的System.Timers.Timer,System.Timers.Timer大致原理和System.Threading.Timer差不多,唯一几处不同的就是:

  • 构造函数不同,构造函数可以什么事情也不做,也可以传入响应间隔时间:System.Timers.Timer timer = new System.Timers.Timer(10);
  • 响应事件的响应函数不在构造函数中设置:timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
  • 声明System.Timers.Timer对象后他不会自动执行,需要调用 timer.Start()或者timer.Enabled = true来启动它, timer.Start()的内部原理还是设置timer.Enabled = true
  • 调用 timer.Stop()或者timer.Enabled = false来停止引发Elapsed事件, timer.Stop()的内部原理还是设置timer.Enabled = false,最重要的是timer.Enabled = false后会取消线程池中当前等待队列中剩余任务的执行。

那么我们来看个例子:

复制代码

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Threading;

namespace ConsoleApplication2
{
class UnSafeTimer
{
static int i = 0;
static System.Timers.Timer timer;
static object mylock = new object();

public static void Init()
{
timer = new System.Timers.Timer(10);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Thread.CurrentThread.IsBackground = false;
int c;

lock (mylock)
{
i++;
c = i;
}

Console.WriteLine(“Now:” + i.ToString());

if (c == 80)
{
timer.Stop();//可应看到System.Timers.Timer的叫停机制比System.Threading.Timer好得多,就算在不安全的代码下Timer也最多多执行一两次(我在试验中发现有时会执行到81或82),说明Stop方法在设置Timer的Enable为false后不仅让Timer不再激发响应事件,还取消了线程池等待队列中等待获得线程的任务,至于那多执行的一两次任务我个人认为是Stop执行过程中会耗费一段时间才将Timer的Enable设置为false,这段时间多余的一两个任务就获得了线程开始执行
}

Thread.Sleep(1000);//等待1000毫秒模拟花时间的代码,注意:这里的等待时间直接决定了80(由于是不安全模式有时会是81或82、83)个任务的执行时间,因为等待时间越短,每个任务就可以越快执行完,那么80个任务中就有越多的任务可以用到前面任务执行完后释放掉的线程,也就有越多的任务不必去线程池申请新的线程避免多等待半秒钟的申请时间
}
}

class SafeTimer
{
static int i = 0;
static System.Timers.Timer timer;

static bool flag = true;
static object mylock = new object();

public static void Init()
{
timer = new System.Timers.Timer(10);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Thread.CurrentThread.IsBackground = false;

lock (mylock)
{
if (!flag)
{
return;
}
i++;

Console.WriteLine(“Now:” + i.ToString());

if (i == 80)
{
timer.Stop();
flag = false;
}
}

Thread.Sleep(1000);//同UnSafeTimer
}

class Program
{
static void Main(string[] args)
{
Console.Write(“是否使用安全Timer>(Y/N)?”);
string Key = Console.ReadLine();

if (Key.ToLower() == “y”)
SafeTimer.Init();
else
UnSafeTimer.Init();

Console.ReadLine();
}
}
}
}

复制代码

这个例子和System.Threading.Timer差不多,这里也分为:安全类SafeTimer和不安全类UnSafeTimer,原因是 timer.Stop()有少许的延迟时间有时任务会执行到81~83,但是就算是不安全方法也就最多多执行几次,不像System.Threading.Timer多执行上百次…

 

在《CLR Via C#》书中,作者推荐使用的是System.Threading.Timer