使用 Topshelf 結合 Quartz.NET 創建 Windows 服務


Ø  前言

之前一篇文章已經介紹了,如何使用 Topshelf 創建 Windows 服務。當時提到還缺少一個任務調度框架,就是 Quartz.NET。而本文就展開對 Quartz.NET 的研究,以及如何使用 Topshelf 結合 Quartz.NET 運行一個定時的 Windows 服務。

 

Ø  本文主要內容

1.   搭建 Topshelf 的運行環境。

2.   編寫一個存儲過程,用於更新商品表中的庫存。

3.   安裝 Quartz 所需的 dll 文件。

4.   創建 Quartz 的配置文件。

5.   創建 Windwos 服務調度程序。

6.   創建作業類,實現 IJob 接口。

7.   開啟 Windows 服務。

 

1.   搭建 Topshelf 的運行環境

1)   創建一個控制台應用程序。

2)   添加 Topshelf 相關的 dll 的引用,可參考使用 Topshelf 創建 Windows 服務

 

2.   編寫一個存儲過程,用於更新商品表中的庫存

1)   首先,創建一張商品表 Goods

IF(OBJECT_ID('Goods', 'U') IS NOT NULL)

    DROP TABLE Goods;

GO

CREATE TABLE Goods

(

    Id int IDENTITY(1, 1) NOT NULL,

    Name nvarchar(30) NOT NULL,

    Inventory int NOT NULL

    CONSTRAINT PK_Goods_Id PRIMARY KEY CLUSTERED

    (

        Id ASC

    ) ON [PRIMARY]

) ON [PRIMARY];

INSERT INTO Goods VALUES('大米', 0),('香蕉', 0),('蘋果', 0);

SELECT * FROM Goods;

clip_image001[1]

 

2)   然后,創建存儲過程 proc_UpdateInventory

IF(OBJECT_ID('proc_UpdateInventory', 'P') IS NOT NULL)

    DROP PROCEDURE proc_UpdateInventory;

GO

CREATE PROCEDURE proc_UpdateInventory(@GoodsId int, @Inventory int)

AS

    UPDATE Goods SET Inventory=@Inventory WHERE Id=@GoodsId;

GO

 

3.   安裝 Quartz 所需的 dll 文件

1)   安裝 Quartz,控制台輸入:Install-Package Quartz

2)   安裝 Common.Logging.Log4Net1211,控制台輸入:Install-Package Common.Logging.Log4Net1211

3)   安裝成功后,將看到如下圖的引用及配置:

clip_image002[1]

 

4.   創建 Quartz 的配置文件

Ø  注意:必須將以下配置文件的“復制到輸出目錄”設置為始終復制。

Ø  關於 Quartz 的配置可參考:Quartz.NET 配置文件詳解

1)   創建 quartz.config 文件,編輯內容:

# You can configure your scheduler in either<quartz> configuration section

# or in quartz properties file

# Configuration section has precedence

 

quartz.scheduler.instanceName = TopshelfAndQuartz

 

# configure thread pool info

quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz

quartz.threadPool.threadCount = 10

quartz.threadPool.threadPriority = Normal

 

# job initialization plugin handles our xml reading, without it defaults are used

quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz

quartz.plugin.xml.fileNames = ~/quartz_jobs.xml

 

# export this server to remoting context

#quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz

#quartz.scheduler.exporter.port = 555

#quartz.scheduler.exporter.bindName = QuartzScheduler

#quartz.scheduler.exporter.channelType = tcp

#quartz.scheduler.exporter.channelName = httpQuartz

 

2)   創建 quartz_jobs.xml 文件,編輯內容:

<?xml version="1.0" encoding="UTF-8"?>

 

<!-- This file contains job definitions in schema version 2.0 format -->

<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">

 

  <processing-directives>

    <overwrite-existing-data>true</overwrite-existing-data>

  </processing-directives>

 

  <schedule>

    <!--該作業用於定時更新商品庫存-->

    <job>

      <name>UpdateInventoryJob</name>

      <group>Update</group>

      <description>定時更新商品庫存</description>

      <job-type>TopshelfAndQuartz.UpdateInventoryJob,TopshelfAndQuartz</job-type>

      <durable>true</durable>

      <recover>false</recover>

    </job>

    <trigger>

      <cron>

        <name>UpdateInventoryTrigger</name>

        <group>Update</group>

        <job-name>UpdateInventoryJob</job-name>

        <job-group>Update</job-group>

        <start-time>2017-12-01T00:00:00+08:00</start-time>

        <cron-expression>0 0/1 * * * ?</cron-expression>

      </cron>

    </trigger>

  </schedule>

 

</job-scheduling-data>

Ø  該文件用於定義每個作業及觸發行為。

 

5.   創建 Windwos 服務調度程序

4)   這里的調度程序是指,當 Windows 服務啟動或停止后,通知 Quartz 調度程序做響應的操作,代碼如下:

/// <summary>

/// 服務運行時。

/// </summary>

public class ServiceRunner : ServiceControl, ServiceSuspend

{

    private readonly IScheduler Scheduler = StdSchedulerFactory.GetDefaultScheduler();

 

    public bool Start(HostControl hostControl)

    {

        //開始調度作業

        Scheduler.Start();

        Log.Logger.Info("開始調度作業");

        return true;

    }

 

    public bool Stop(HostControl hostControl)

    {

        //停止調度作業

        Scheduler.Shutdown(false);  //false: 表示當停止服務時,所有正則在執行的作業也立即停止

        Log.Logger.Info("停止調度作業");

        return true;

    }

 

    public bool Continue(HostControl hostControl)

    {

        //所有調度作業重新開始

        Scheduler.ResumeAll();

        Log.Logger.Info("所有調度作業重新開始");

        return true;

    }

 

    public bool Pause(HostControl hostControl)

    {

        //暫停所有調度作業

        Scheduler.ResumeAll();

        Log.Logger.Info("暫停所有調度作業");

        return true;

    }

}

5)   注意:只有實現了 ServiceSuspend 接口,服務才支持暫停與恢復操作,否則會報錯。

 

6.   創建作業類,實現 IJob 接口

6)   當作業被觸發時,將調用對應作業的 Execute() 方法。

/// <summary>

/// 更新庫存作業。

/// </summary>

public class UpdateInventoryJob : IJob

{

    /// <summary>

    /// 作業被觸發時執行該方法。

    /// </summary>

    public void Execute(IJobExecutionContext context)

    {

        try

        {

            //模擬調用存儲過程,更新商品庫存

            string connStr = @"Data Source=127.0.0.1\MYMSSQLSERVER08;Initial Catalog=MyDB;Persist Security Info=True;User ID=sa;Password=xxxxxx;";

            using (SqlConnection conn = new SqlConnection(connStr))

            {

                using (SqlCommand cmd = new SqlCommand())

                {

                    conn.Open();

                    cmd.Connection = conn;

                    cmd.CommandType = CommandType.StoredProcedure;

                    cmd.CommandText = "proc_UpdateInventory";

                    Random random = new Random();

                    SqlParameter[] paras = new SqlParameter[]

                    {

                        new SqlParameter()

                        {

                            ParameterName = "@GoodsId",

                            SqlDbType = SqlDbType.Int,

                            Value =  random.Next(1, 4)

                        },

                        new SqlParameter()

                        {

                            ParameterName = "@Inventory",

                            SqlDbType = SqlDbType.Int,

                            Value = random.Next(1, 100)

                        }

                    };

                    cmd.Parameters.AddRange(paras);

                    int rowCount = cmd.ExecuteNonQuery();   //exec proc_UpdateInventory @GoodsId=1,@Inventory=25

                    if (rowCount > 0)

                        Log.Logger.InfoFormat("商品:{0},庫存已更新,新的庫存為:{1}", paras[0].Value, paras[1].Value);

                    else

                        Log.Logger.InfoFormat("更新商品庫失敗,無受影響記錄:{0}", rowCount);

                }

            }

        }

        catch (Exception ex)

        {

            Log.Logger.ErrorFormat("UpdateInventoryJob 作業執行異常:{0}", ex);

        }

    }

}

 

7.   開啟 Windows 服務

1)   首先在 Main() 方法中加入如下代碼:

static void Main(string[] args)

{

    var configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config"));

    log4net.Config.XmlConfigurator.ConfigureAndWatch(configFile);

 

    Log.Logger.Info("服務開始運行");

    HostFactory.Run(o =>

    {

        //o.UseLog4Net(); //這里需要使用 log4net, Version=1.2.15.0 的版本,當前版本不兼容所以注釋掉

        o.Service<ServiceRunner>();

        o.SetServiceName("TopshelfAndQuartzService");

        o.SetDisplayName("庫存更新服務");

        o.SetDescription("該服務用於定時更新商品庫存");

        o.EnablePauseAndContinue();

    });

}

2)   安裝服務並啟動

安裝服務的具體操作可參考:使用 Topshelf 創建 Windows 服務

3)   啟動服務后,等待3分鍾,依次對服務進行暫停 -> 恢復 -> 停止操作,將看到如下結果:

1.   Log

clip_image003[1]

2.   Data

clip_image004[1]

 

Ø  總結

本文,使用 Topshelf 結合 Quartz 搭建了一個 Windows 服務,用於定時調用存儲過程更新商品庫存。可見 Topshelf Quartz 的結合是天衣無縫,非常適合在平時的開發工作中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM