Chemmy's Blog

chengming0916@outlook.com

同学们有福了,花了一些时间,重新整理了一个最完整的Mybatis Generator(简称MBG)的最完整配置文件,带详解,再也不用去看EN的User Guide了;

复制代码

<generatorConfiguration>

<context id=“mysql” defaultModelType=“hierarchical” targetRuntime=“MyBatis3Simple” >

<!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表;
    一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖 \-->
<property name\="autoDelimitKeywords" value\="false"/>
<!-- 生成的Java文件的编码 \-->
<property name\="javaFileEncoding" value\="UTF-8"/>
<!-- 格式化java代码 \-->
<property name\="javaFormatter" value\="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!-- 格式化XML代码 \-->
<property name\="xmlFormatter" value\="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

<!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是\`反引号; \-->
<property name\="beginningDelimiter" value\="\`"/>
<property name\="endingDelimiter" value\="\`"/>

<!-- 必须要有的,使用这个配置链接数据库
    @TODO:是否可以扩展 \-->
<jdbcConnection driverClass\="com.mysql.jdbc.Driver" connectionURL\="jdbc:mysql:///pss" userId\="root" password\="admin"\>
    <!-- 这里面可以设置property属性,每一个property属性都设置到配置的Driver上 \-->
</jdbcConnection\>

<!-- java类型处理器 
    用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
    注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型; \-->
<javaTypeResolver type\="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl"\>
    <!-- true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型
        false:默认,
            scale>0;length>18:使用BigDecimal;
            scale=0;length\[10,18\]:使用Long;
            scale=0;length\[5,9\]:使用Integer;
            scale=0;length<5:使用Short; \-->
    <property name\="forceBigDecimals" value\="false"/>
</javaTypeResolver\>

<!-- java模型创建器,是必须要的元素
    负责:1,key类(见context的defaultModelType);2,java类;3,查询类
    targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制;
    targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录 \-->
<javaModelGenerator targetPackage\="com.\_520it.mybatis.domain" targetProject\="src/main/java"\>
    <!-- for MyBatis3/MyBatis3Simple
        自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter; \-->
    <property name\="constructorBased" value\="false"/>

    <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false \-->
    <property name\="enableSubPackages" value\="true"/>

    <!-- for MyBatis3 / MyBatis3Simple
        是否创建一个不可变的类,如果为true,
        那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类 \-->
    <property name\="immutable" value\="false"/>

    <!-- 设置一个根对象,
        如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;在Table的rootClass属性中可以覆盖该选项
        注意:如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括:
            1,属性名相同,类型相同,有相同的getter/setter方法; \-->
    <property name\="rootClass" value\="com.\_520it.mybatis.domain.BaseDomain"/>

    <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 \-->
    <property name\="trimStrings" value\="true"/>
</javaModelGenerator\>

<!-- 生成SQL map的XML文件生成器,
    注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口),
        或者只使用Mapper接口+Annotation,所以,如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置
    targetPackage/targetProject:同javaModelGenerator \-->
<sqlMapGenerator targetPackage\="com.\_520it.mybatis.mapper" targetProject\="src/main/resources"\>
    <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false \-->
    <property name\="enableSubPackages" value\="true"/>
</sqlMapGenerator\>

<!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口 
    targetPackage/targetProject:同javaModelGenerator
    type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
        1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
        2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
        3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
    注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER \-->
<javaClientGenerator targetPackage\="com.\_520it.mybatis.mapper" type\="ANNOTATEDMAPPER" targetProject\="src/main/java"\>
    <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false \-->
    <property name\="enableSubPackages" value\="true"/>

    <!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查
    <property name="rootInterface" value=""/> \-->
</javaClientGenerator\>

<!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素
    选择的table会生成一下文件:
    1,SQL map文件
    2,生成一个主键类;
    3,除了BLOB和主键的其他字段的类;
    4,包含BLOB的类;
    5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
    6,Mapper接口(可选)

    tableName(必要):要生成对象的表名;
    注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会
        根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
        1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询;
        2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
        3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
        4,否则,使用指定的大小写格式查询;
    另外的,如果在创建表的时候,使用的""把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
    这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;

    可选:
    1,schema:数据库的schema;
    2,catalog:数据库的catalog;
    3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias\_actualColumnName
    4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面;
    5,enableInsert(默认true):指定是否生成insert语句;
    6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
    7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
    8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
    9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
    10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
    11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询);
    12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性);
    13,modelType:参考context元素的defaultModelType,相当于覆盖;
    14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是\`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性)
    15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性

    注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写; \-->
<table tableName\="userinfo" \>

    <!-- 参考 javaModelGenerator 的 constructorBased属性\-->
    <property name\="constructorBased" value\="false"/>

    <!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema; \-->
    <property name\="ignoreQualifiersAtRuntime" value\="false"/>

    <!-- 参考 javaModelGenerator 的 immutable 属性 \-->
    <property name\="immutable" value\="false"/>

    <!-- 指定是否只生成domain类,如果设置为true,只生成domain类,如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 \-->
    <property name\="modelOnly" value\="false"/>

    <!-- 参考 javaModelGenerator 的 rootClass 属性 
    <property name="rootClass" value=""/> \-->

    <!-- 参考javaClientGenerator 的  rootInterface 属性
    <property name="rootInterface" value=""/> \-->

    <!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,而不是table元素上的catalog 
    <property name="runtimeCatalog" value=""/> \-->

    <!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,而不是table元素上的schema 
    <property name="runtimeSchema" value=""/> \-->

    <!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,而不是table元素上的tablename 
    <property name="runtimeTableName" value=""/> \-->

    <!-- 注意,该属性只针对MyBatis3Simple有用;
        如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件; \-->
    <property name\="selectAllOrderByClause" value\="age desc,username asc"/>

    <!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法,比如BORN\_DATE,生成的属性名字就是BORN\_DATE,而不会是bornDate \-->
    <property name\="useActualColumnNames" value\="false"/>

    <!-- generatedKey用于生成生成主键的方法,
        如果设置了该元素,MBG会在生成的<insert>元素中生成一条正确的<selectKey>元素,该元素可选
        column:主键的列名;
        sqlStatement:要生成的selectKey语句,有以下可选项:
            Cloudscape:相当于selectKey的SQL为: VALUES IDENTITY\_VAL\_LOCAL()
            DB2       :相当于selectKey的SQL为: VALUES IDENTITY\_VAL\_LOCAL()
            DB2\_MF    :相当于selectKey的SQL为:SELECT IDENTITY\_VAL\_LOCAL() FROM SYSIBM.SYSDUMMY1
            Derby      :相当于selectKey的SQL为:VALUES IDENTITY\_VAL\_LOCAL()
            HSQLDB      :相当于selectKey的SQL为:CALL IDENTITY()
            Informix  :相当于selectKey的SQL为:select dbinfo('sqlca.sqlerrd1') from systables where tabid=1
            MySql      :相当于selectKey的SQL为:SELECT LAST\_INSERT\_ID()
            SqlServer :相当于selectKey的SQL为:SELECT SCOPE\_IDENTITY()
            SYBASE      :相当于selectKey的SQL为:SELECT @@IDENTITY
            JDBC      :相当于在生成的insert元素上添加useGeneratedKeys="true"和keyProperty属性
    <generatedKey column="" sqlStatement=""/> \-->

    <!-- 该元素会在根据表中列名计算对象属性名之前先重命名列名,非常适合用于表中的列都有公用的前缀字符串的时候,
        比如列名为:CUST\_ID,CUST\_NAME,CUST\_EMAIL,CUST\_ADDRESS等;
        那么就可以设置searchString为"^CUST\_",并使用空白替换,那么生成的Customer对象中的属性名称就不是
        custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email;

        注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的,
        如果使用了columnOverride元素,该属性无效;

    <columnRenamingRule searchString="" replaceString=""/> \-->

     <!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性;
         column:要重新设置的列名;
         注意,一个table元素中可以有多个columnOverride元素哈~ \-->
     <columnOverride column\="username"\>
         <!-- 使用property属性来指定列要生成的属性名称 \-->
         <property name\="property" value\="userName"/>

         <!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名
         <property name="javaType" value=""/> \-->

         <!-- jdbcType用于指定该列的JDBC类型 
         <property name="jdbcType" value=""/> \-->

         <!-- typeHandler 用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名
             注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler
             只会生成类似:where id = #{id,jdbcType=BIGINT,typeHandler=com.\_520it.mybatis.MyTypeHandler}的参数描述
         <property name="jdbcType" value=""/> \-->

         <!-- 参考table元素的delimitAllColumns配置,默认为false
         <property name="delimitedColumnName" value=""/> \-->
     </columnOverride\>

     <!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,生成的SQL中,都不会有该列出现 
         column:指定要忽略的列的名字;
         delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false

         注意,一个table元素中可以有多个ignoreColumn元素
     <ignoreColumn column="deptId" delimitedColumnName=""/> \-->
</table\>

</context>

</generatorConfiguration>

复制代码

引言

在软件开发中,单例模式(Singleton Pattern) 是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。它适用于需要严格控制资源访问的场景,例如数据库连接池、配置管理器或任务调度器等。本文将详细介绍单例模式的核心思想,并展示其在 **C#、Python、Golang 中的实现方式。

单例模式的主要特点包括:

  • 唯一性:类只有一个实例对象
  • 自创建:类自行创建自己的实例
  • 全局访问:提供一个全局访问点来获取该实例

特点

  • 唯一性:类自身负责创建和管理实例。
  • 延迟加载:实例通常在第一次使用时创建(懒汉式)。
  • 线程安全:在多线程环境中需确保实例的唯一性。
  • 不可克隆/序列化:避免通过克隆或反序列化创建新实例。

单例模式的实现方式

C# 实现

C# 中的单例模式通常通过 双重检查锁定(Double-Check Locking) 实现,以确保线程安全和延迟加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public sealed class Singleton
{
// 使用 volatile 保证多线程下的可见性
private static volatile Singleton _instance;
private static readonly object _lock = new object();

// 私有构造函数
private Singleton() { }


public static Singleton GetInstance()
{
// 第一次检查,避免不必要的锁定
if (_instance == null)
{
// 锁定操作
lock (_lock)
{
// 第二次检查,确保多线程安全
if (_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}

饿汉式(立即加载)

1
2
3
4
5
6
7
8
9
10
11
12
13
public sealed class Singleton
{
// 静态初始化,CLR保证线程安全
private static readonly Singleton _instance = new Singleton();

// 私有构造函数
private Singleton() { }

public static Singleton GetInstance()
{
return _instance;
}
}

Python 实现

Python 的模块天然支持单例,但也可以通过类实现。以下是一个线程安全的懒汉式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import threading

class Singleton:
_instance_lock = threading.Lock() # 线程锁

def __init__(self):
# 初始化逻辑
pass

def __new__(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
with cls._instance_lock: # 确保线程安全
if not hasattr(Singleton, "_instance"):
Singleton._instance = super().__new__(cls)
return Singleton._instance

# 使用示例
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # 输出: True

饿汉式(模块级单例)

1
2
3
4
5
6
7
8
9
# singleton.py
class Singleton:
def __init__(self):
pass

instance = Singleton()

# 使用示例
from singleton import instance

装饰器实现

1
2
3
4
5
6
7
8
9
10
11
def singleton(cls): 
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper

@singleton
class MySingleton:
pass

Golang 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"sync"
)

type Singleton struct{}

var (
instance *Singleton
once sync.Once
)

func GetInstance() *Singleton {
// sync.Once 确保代码只执行一次,线程安全
once.Do(func() {
instance = &Singleton{}
})
return instance
}

// 使用示例
func main() {
s1 := GetInstance()
s2 := GetInstance()
println(s1 == s2) // 输出: true
}

饿汉式

1
2
3
4
5
6
7
8
9
package main

type Singleton struct{}

var instance = &Singleton{}

func GetInstance() *Singleton {
return instance
}

单例模式的优缺点

优点

  • 控制实例数量:确保全局唯一性,避免资源浪费。
  • 灵活扩展:可通过子类化或组合模式扩展功能。
  • 全局访问:简化了对共享资源的访问。

缺点

  • 违反单一职责原则:类负责管理自己的实例,增加了耦合。
  • 测试困难:全局状态可能导致单元测试难以隔离。
  • 生命周期管理:实例与程序生命周期一致,可能占用过多内存。.

应用场景

  • 资源管理器:如文件系统、数据库连接池。
  • 配置中心:全局配置对象,避免重复加载配置。
  • 缓存服务:单点缓存,减少内存开销。
  • 日志记录器:统一日志输出,避免多线程冲突。

总结

单例模式是一种简单但强大的设计模式,适用于需要严格控制实例数量的场景。不同编程语言的实现方式各有特色:

  • C# 通过 lock 和 volatile 保证线程安全。
  • Python 可利用模块的天然单例特性。
  • Golang 使用 sync.Once 实现原子初始化。
  • C/C++ 通过静态局部变量或互斥锁实现线程安全。

实现要点总结

  1. 私有构造函数:防止外部直接实例化
  2. 静态实例变量:保存唯一的实例
  3. 全局访问点:提供获取实例的静态方法
  4. 线程安全:在多线程环境下需要考虑线程安全问题

选择建议

  • 懒汉式:适用于实例创建开销较大,且可能不被使用的场景
  • 饿汉式:适用于实例创建开销小,且一定会被使用的场景
  • 双重检查锁定:适用于需要兼顾性能和线程安全的场景

在实际开发中,需根据语言特性和具体需求选择合适的实现方式,同时注意避免过度使用单例模式,以免引入全局状态带来的复杂性。

1.Quartz的作用

定时自动执行任务

2.预备

相关包官方网站

1
2
quartz2.2.1
quartz-jobs2.2.1

POM文件

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>

3.Quartz核心

3.1.Job接口

被调度的任务,只有一个方法execute(JobExecutionContext xontext),Job运行时的信息保存在JobDataMap中

3.2.JobDetail类

实现Job接口,用来描述Job的相关信息,包含Name,Group,JobDataMap等

3.3 JobExecutionContext类

定时程序执行的run-time的上下文环境,用于得到Job的名字、配置的参数等

3.3 JobDataMap类

用来描述一个作业的参数,参数可以为金和基本类型或者某个对象的引用

3.3 JobListener接口

监听作业状态

3.3 TriggaerListener接口

监听触发器状态

3.3 JobStore

3.3.Tigger抽象类

触发器,描述执行Job的触发规则,有SimpleTrigger和CronTrigger两个子类

3.3.1.SimpleTrigger类

继承自Trigger类,每隔xx毫秒/秒执行一次,主要实现固定一次或者固定时间周期类任务的触发

3.3.2.CronTrigger类

继承自Trigger类,使用Cron表达式,实现各种复杂时间规则调度方案,如每天的某个时间,或每周的某几天触发执行之类

3.4.Calendar包

一些日历特定时间点的集合,包内包含以下几个类

3.4.1 BaseCalendar类

3.4.2 AnnualCalendar类

排除每一年中指定的一天或者多天

3.4.3 CalendarComparator类

3.4.4 CronCalendar类

使用表达式排除某时间段不执行

3.4.5 DailyCalendar类

指定的时间范围内每天不执行

3.4.6 HolidayCalendar类

排除节假日

3.4.7 MonthlyCalendar类

配出月份中的数天

3.4.8 WeeklyCalendar类

排除没周中的一天或者多天

3.5.Scheduler类

任务调度器,代表一个Quartz独立容器。

Scheduler可以将JobDetail和Trigger绑定,当Trigger触发时,对应的Job就会被执行,Job和Trigger是1:n(一对多)的关系

3.6Misfire类

错误的任务,本该执行单没有执行的任务调度

4.实现

1.单任务实现

1.定义一个任务,新建任务类继承自Job类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class DemoJob implements Job {

@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()));
}

}

2.新建类执行这个任务(SimpleTrigger)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com;

import java.util.Date;

import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzDemo {

public void simpleRun() throws SchedulerException {

SchedulerFactory factory = new StdSchedulerFactory();



Date runTime = DateBuilder.evenSecondDateAfterNow();


JobDetail jobDetail = JobBuilder.newJob(DemoJob.class)
.withIdentity("demo_job", "demo_group")
.build();

Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("demo_trigger", "demo_group")

.startAt(new Date())
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(1)
.withRepeatCount(5)
).build();


Scheduler scheduler = factory.getScheduler();

scheduler.scheduleJob(jobDetail,trigger);
System.out.println(jobDetail.getKey() + " 运行在: " + runTime);
scheduler.start();
}

public static void main(String[] args) {
QuartzDemo demo = new QuartzDemo();
try {
demo.simpleRun();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}

2.多任务实现

  1. 测试任务类
    新建两个DemoJonOne和DemoJobTwo,都实现Job接口,内容如下
1
2
3
4
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date())+" Runed "+getClass().getName());
}

2.新建QuartzUtil类,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com;

import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzUtil {
private final static String JOB_GROUP_NAME = "QUARTZ_JOBGROUP_NAME";
private final static String TRIGGER_GROUP_NAME = "QUARTZ_TRIGGERGROUP_NAME";


public static void addJob(String jobName, String triggerName, Class<? extends Job> jobClass, int seconds)
throws SchedulerException {


SchedulerFactory sf = new StdSchedulerFactory();

Scheduler sche = sf.getScheduler();

JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, JOB_GROUP_NAME).build();

Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName, TRIGGER_GROUP_NAME)
.startNow()
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(seconds)
.repeatForever()
).build();


sche.scheduleJob(jobDetail, trigger);

sche.start();
}

public static void main(String[] args) {
try {

QuartzUtil.addJob("job1", "trigger1", DemoJobOne.class, 2);


QuartzUtil.addJob("Job2", "trigger2", DemoJobTwo.class, 5);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}

以上方法属于手动调用,如果是web项目中就不同了
添加POM

1
2
3
4
5
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.quartz.SchedulerException;

import com.DemoJobOne;
import com.DemoJobTwo;
import com.QuartzUtil;

public class InitServlet extends HttpServlet {

private static final long serialVersionUID = 8507188690597926975L;


public void init() throws ServletException {
try {

QuartzUtil.addJob("job1", "trigger1", DemoJobOne.class, 2);

QuartzUtil.addJob("Job2", "trigger2", DemoJobTwo.class, 5);
} catch (SchedulerException e) {
e.printStackTrace();
}
}

}

2.注册servlet

1
2
3
4
5
6
7
8
9
10
11
<servlet>
<servlet-name>InitServlet</servlet-name>
<servlet-class>servlet.InitServlet</servlet-class>

<load-on-startup>0</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>InitServlet</servlet-name>
<url-pattern>/InitServlet</url-pattern>
</servlet-mapping>

3.复杂规则任务调度(CronTrigger)

在每分钟的1-30秒执行示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com;

import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class CronTriggerDemo {
public static void main(String[] args) throws SchedulerException {

SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();

JobDetail job = JobBuilder
.newJob(DemoJobOne.class)
.withIdentity("job","group")
.build();

Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("trigger", "group")
.startNow().withSchedule(
CronScheduleBuilder
.cronSchedule("1-30 * * * * ?")
).build();

scheduler.scheduleJob(job,trigger);
scheduler.start();

}
}

5.Cron表达式

规则

格式

1
s M h d m w [y]

s:seconds,取值0-59,允许- * /;

M:minutes,取值0-59,允许- * /;

h:hour,取值0-23,允许- * /;

d:day of month,取值1-31,允许- * ? / L W;

m:month,取值1-12/JAN-DEC,允许- * /;

w:day of week,取值1-7/SUN-SAT,允许- * ? / L #;

y:year,可选,取值empty、1970-2099,允许- * /;

符号解释

、 指定枚举值,如在秒字段使用10、12,则表示只有第10秒和第12秒执行
- 指定区间范围,配合使用,如在小时字段使用10-12,表示在10、11、12时都会触发

* 代表所有值,单独使用,如在秒字段使用,表示每秒触发

? 代表不确定值,单独使用,不用关心的值

/ 用于递增触发,配合使用,n/m,从n开始,每次增加m,如在秒字段设置5/15,表示从第5秒开始,每15秒触发一次

L 表示最后,单独使用,如在秒字段使用,代表第59秒触发,如果在前面加上数字,则表示该数据的最后一个,如在周字段使用6L,则表示本月最后一个周五
W 表示最近的工作日,不会跨月,比如30W,30号是周六,则不会顺延至下周一来执行,如在月字段使用15W,则表示到本月15日最近的工作日(周一到周五)
# 用来指定x的第n个工作日,如在周字段使用6#3则表示该月的第三个星期五

月取值

一月:JAN/0
二月:FEB/1
三月:MAR/2
四月:APR/3
五月:MAY/4
六月:JUN/5
七月:JUL/6
八月:AUG/7
九月:SEP/8
十月:OCT/9
十一月:NOV/10
十二月:DEC/11

周取值

周日:SUN/1
周一:MON/2
周二:TUE/3
周三:WED/4
周四:THU/5
周五:FRI/6
周六:SAT/7

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0/20 * * * * ? 每20秒执行一次
1-30 * * * * ? 在1-30秒执行
15 0/2 * * * ? 偶数分钟的第15秒执行
0 0/2 8-17 * * ? 从8时到17时 ,每个偶数分钟执行一次
0 0/3 17-23 * * ? 从17时到23时,每3分钟运行一次
0 0 10am 1,15 * ? 每个月的1号和15号的上午10点 运行
0,30 * * ? * MON-FRI 周一至周五,每30秒运行一次
0,30 * * ? * SAT,SUN 周六、周日,每30秒运行一次
0 0 12 * * ? 每天12点触发
0 15 10 ? * * 每天10点15分触发
0 15 10 * * ? 每天10点15分触发
0 15 10 * * ? * 每天10点15分触发
0 15 10 * * ? 2005 2005年每天10点15分触发
0 * 14 * * ? 每天下午的 2点到2点59分每分触发
0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发) 每天下午的 18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED 3月分每周三下午的 2点10分和2点44分触发
0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发
0 15 10 15 * ? 每月15号上午10点15分触发
0 15 10 L * ? 每月最后一天的10点15分触发
0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发
0 15 10 ? * 6L 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每月的第三周的星期五开始触发
0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)

6.Spring整合Quartz

需要Spring-context-support包支持,POM如下

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>

新建两种Job测试类–>DemoSimpleJob类和DemoCronJob类,并继承自QuartzJobBean,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class DemoJob extends QuartzJobBean {

@Override
protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
System.out.println(new SimpleDateFormat("hh:mm:ss").format(new Date()) + " 输出自:" + getClass().getName());
}

}

配置spring bean如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">




<bean id="demoCronJob"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.DemoCronJob" />
</bean>
<bean id="demoSimpleJob"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.DemoSimpleJob" />
</bean>


<bean id="simpleTrigger"
class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="demoSimpleJob" />
<property name="startDelay" value="1000" />
<property name="repeatInterval" value="2000" />
</bean>
<bean id="cornTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="demoCronJob" />
<property name="cronExpression" value="1-30 * * * * ?" />
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cornTrigger" />
<ref bean="simpleTrigger" />
</list>
</property>
</bean>

启动

1
2
3
4
5
6
7
8
9
10
11
package com;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

}
}

有待补充

  1. WinForm程序

1)第一种方法,使用委托:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

private delegate void SetTextCallback(string text);
private void SetText(string text)
{
// InvokeRequired需要比较调用线程ID和创建线程ID
// 如果它们不相同则返回true
if (this.txt_Name.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else {
this.txt_Name.Text = text;
}
}

2)第二种方法,使用匿名委托

1
2
3
4
5
6
7
8
9
10
11
12
private void SetText(Object obj)
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(delegate {
this.txt_Name.Text = obj;
}));
}
else {
this.txt_Name.Text = obj;
}
}

这里说一下BeginInvoke和Invoke和区别:BeginInvoke会立即返回,Invoke会等执行完后再返回。

  1. WPF程序

1)可以使用Dispatcher线程模型来修改

如果是窗体本身可使用类似如下的代码:

this.lblState.Dispatcher.Invoke(new Action(delegate
{
this.lblState.Content = “状态:” + this._statusText;
}));

那么假如是在一个公共类中弹出一个窗口、播放声音等呢?这里我们可以使用:System.Windows.Application.Current.Dispatcher,如下所示

复制代码

System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>
{
if (path.EndsWith(“.mp3”) || path.EndsWith(“.wma”) || path.EndsWith(“.wav”))
{
_player.Open(new Uri(path));
_player.Play();
}
}));

关键问题:多个视频同时播放,以上几种方法不足以解决,多个视频播放中主界面卡死和播放显示刷新不了的问题。

目前笔者的解决方法是

 pinturebox.CreateGraphics().DrawImage(imgSrc.Bitmap, new System.Drawing.Rectangle(0, 0, pinturebox.Width, pinturebox.Height));

EmguCV中的Capture类可以完成视频文件的读取,并捕捉每一帧,可以利用Capture类完成实现WinForm中视频检测跟踪环境的搭建。本文只实现最简陋的WinForm + EmguCV上的avi文件读取和播放框架,复杂的检测和跟踪算法在之后添加进去。

        这里使用WinForm实现视频的播放,主要是PictureBox类,它是支持基于事件的异步模式的典型组件,不使用EmguCV自带的UI控件等。

效果图

图1.效果图

        直接在UI线程中完成视频的播放的话整个程序只有一个线程,由于程序只能同步执行,播放视频的时候UI将停止响应用户的输入,造成界面的假死。所以视频的播放需要实现异步模式。主要有三种方法:第一是使用异步委托;第二种是使用BackgroundWorker组件;最后一种就是使用多线程(不使用CheckForIllegalCrossThreadCalls =false的危险做法)。

        Windows窗体控件,唯一可以从创建它的线程之外的线程中调用的是Invoke()、BegionInvoke()、EndInvoke()方法和InvokeRequired属性。其中BegionInvoke()、EndInvoke()方法是Invoke()方法的异步版本。这些方法会切换到创建控件的线程上,以调用赋予一个委托参数的方法,该委托参数可以传递给这些方法。

        (一)   使用多线程
        首先定义监控的类及其对应的事件参数类和异常类:
        判断是否继续执行的布尔型成员会被调用线程改变,因此声名为volatile,不进行优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/// <summary>
/// 红外检测子。
/// </summary>
public class ThermalSurveillant
{
#region Private Fields

/// <summary>
/// 是否停止线程,此变量供多个线程访问。
/// </summary>
private volatile bool shouldStop = false;

#endregion
#region Public Properties

#endregion
#region Public Events

/// <summary>
/// 帧刷新事件。
/// </summary>
public EventHandler<FrameRefreshEventArgs> FrameRefresh;

/// <summary>
/// 播放完成。
/// </summary>
public EventHandler<CompletedEventArgs> Completed;

#endregion
#region Protected Methods

/// <summary>
/// 处理帧刷新事件。
/// </summary>
/// <param name="e"></param>
protected virtual void OnFrameRefresh(FrameRefreshEventArgs e)
{
if (this.FrameRefresh != null)
{
this.FrameRefresh(this, e);
}
}

/// <summary>
/// 处理视频读完事件。
/// </summary>
/// <param name="e"></param>
protected virtual void OnCompleted(CompletedEventArgs e)
{
if (this.Completed != null)
{
this.Completed(this, e);
}
}

#endregion
#region Public Methods

/// <summary>
/// 视频监控。
/// </summary>
/// <param name="capture">捕捉。</param>
public void DoSurveillance(Object oCapture)
{
Capture capture = oCapture as Capture;
int id = 1;
if (capture == null)
{
throw new InvalidCaptureObjectException("传递的Capture类型无效。");
}
while (!shouldStop)
{
Image<Bgr, byte> frame = capture.QueryFrame();
if (frame != null)
{
FrameRefreshEventArgs e = new FrameRefreshEventArgs(frame.ToBitmap(), id++);
// 触发刷新事件
this.OnFrameRefresh(e);
}
else
{
break;
}
}
// 触发完成事件
this.OnCompleted(new CompletedEventArgs(id));
}

/// <summary>
/// 请求停止线程。
/// </summary>
public void Cancel()
{
this.shouldStop = true;
}

#endregion
}

        UI线程中启动播放线程:

声明:

1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>
/// 监控线程。
/// </summary>
private Thread threadSurveillance = null;
/// <summary>
/// 捕获视频帧。
/// </summary>
private Capture captureSurveillance;
/// <summary>
/// 监控子。
/// </summary>
private ThermalSurveillant surveillant = new ThermalSurveillant();

读入视频文件:

1
2
3
4
5
captureSurveillance = new Capture(this.videoFilePath);
captureSurveillance.SetCaptureProperty(CAP_PROP.CV_CAP_PROP_FRAME_WIDTH, this.width);
captureSurveillance.SetCaptureProperty(CAP_PROP.CV_CAP_PROP_FRAME_HEIGHT, this.height);
Image<Bgr, byte> frame = captureSurveillance.QueryFrame();
this.pictureBox.Image = frame.ToBitmap();

播放视频文件:

        UI线程中响应监控类的事件:

定义异步调用的委托:

添加事件委托:

1
2
this.surveillant.FrameRefresh += OnRefreshFrame;
this.surveillant.Completed += OnCompleted;

        以下方法中都是由监控线程中的事件委托方法,应该使用BeginInvoke方法,这样可以优雅的结束线程,如果使用Invoke方法,则调用方式为同步调用,此时如果使用Thread.Join()方法终止线程将引发死锁(正常播放没有问题),Thread.Join()方法的使用使调用线程阻塞等待当前线程完成,在这里即UI线程阻塞等待监控线程完成,而监控线程中又触发UI线程中pictureBox的刷新,使用Invoke方法就造成了监控线程等待UI线程刷新结果,而UI线程已经阻塞,形成了死锁。死锁时只能用Thread.Abort()方法才能结束线程。或者直接强制结束应用程序。

        使用BeginInvoke方法时为异步调用,监控线程不等待刷新结果直接继续执行,可以正常结束。结束后UI才进行刷新,不会造成死锁。

图2.线程关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/// <summary>
/// 刷新UI线程的pixtureBox的方法。
/// </summary>
/// <param name="frame">要刷新的帧。</param>
private void RefreshFrame(Bitmap frame)
{
this.pictureBox.Image = frame;
// 这里一定不能刷新!2012年8月2日1:50:16
//this.pictureBox.Refresh();
}


/// <summary>
/// 响应pictureBox刷新。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnRefreshFrame(object sender, FrameRefreshEventArgs e)
{
// 判断是否需要跨线程调用
if (this.pictureBox.InvokeRequired == true)
{
FrameRefreshDelegate fresh = this.RefreshFrame;
this.BeginInvoke(fresh, e.Frame);
}
else
{
this.RefreshFrame(e.Frame);
}
}


/// <summary>
/// 响应Label刷新信息。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnCompleted(object sender, CompletedEventArgs e)
{
// 判断是否需要跨线程调用
CompletedDelegate fresh = this.RefreshStatus;
string message = "视频结束,共 " + e.FrameCount + " 帧。";
this.BeginInvoke(fresh, message);
}
  

关闭时需要中止播放线程之后再退出:

/// <summary>
/// 关闭窗体时发生。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnFormClosed(object sender, FormClosedEventArgs e)
{
// 检测子算法请求终止
surveillant.Cancel();

// 阻塞调用线程直到检测子线程终止
if (threadSurveillance != null)
{
if (threadSurveillance.IsAlive == true)
{
threadSurveillance.Join();
}
}
}

        (二)   使用异步委托

        创建线程的一个更简单的方法是定义一个委托,并异步调用它。委托是方法的类型安全的引用。Delegate类还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// asynchronous by using a delegate
PlayVideoDelegate play = this.PlayVideoFile;
IAsyncResult status = play.BeginInvoke(null, null);

/// <summary>
/// 播放视频文件。
/// </summary>
private void PlayVideoFile()
{
while (true)
{
Image<Bgr, byte> frame = capture.QueryFrame();
if (frame != null)
{
Image<Gray, byte> grayFrame = frame.Convert<Gray, byte>();
grayFrame.Resize(this.width, this.height, INTER.CV_INTER_CUBIC);
RefreshPictureBoxDelegate fresh = this.RefreshPictureBox;
try
{
this.BeginInvoke(fresh, grayFrame.ToBitmap());
}
catch (ObjectDisposedException ex)
{
Thread.CurrentThread.Abort();
}
}
else
{
break;
}
}
}

/// <summary>
/// 刷新UI线程的pixtureBox的方法。
/// </summary>
/// <param name="frame">要刷新的帧。</param>
private void RefreshPictureBox(Bitmap frame)
{
this.pictureBox.Image = frame;
}

        (三)   使用BackgroundWorker组件

        BackgroundWorker类是异步事件的一种实现方案,异步组件可以选择性的支持取消操作,并提供进度信息。RunWorkerAsync()方法启动异步调用。CancelAsync()方法取消。

图3.BackgroundWorker组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/// <summary>
/// 播放视频文件。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void detectItemPlay_Click(object sender, EventArgs e)
{
if (this.videoFilePath != null)
{
// run async
this.backgroundWorker.RunWorkerAsync(capture);
}
}

/// <summary>
/// 异步调用。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnDoWork(object sender, DoWorkEventArgs e)
{
Emgu.CV.Capture capture = e.Argument as Emgu.CV.Capture;
while (!e.Cancel)
{
Image<Bgr, byte> frame = capture.QueryFrame();
if (frame != null)
{
Image<Gray, byte> grayFrame = frame.Convert<Gray, byte>();
grayFrame.Resize(this.width, this.height, INTER.CV_INTER_CUBIC);
if (this.backgroundWorker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
if (this.pictureBox.InvokeRequired == true)
{
RefreshPictureBoxDelegate fresh = this.RefreshPictureBox;
this.BeginInvoke(fresh, grayFrame.ToBitmap());
}
else
{
this.RefreshPictureBox(grayFrame.ToBitmap());
}
}
}
else
{
break;
}
}
}

/// <summary>
/// 关闭窗体时发生。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
if (this.backgroundWorker.IsBusy)
{
this.backgroundWorker.CancelAsync();
}
}
0%