我們先來介紹一下使用它的好處,以下論述參考自其他大神。
topshelf是創建windows服務的一種方式,相比原生實現ServiceBase、Install.Installer更為簡單方便, 我們只需要幾行代碼即可實現windows服務的開發。
topshelf本身支持windows及linux下mono上部署安裝,同樣也是開源的。
topshelf相對原生來說,調試起來比較方便,可以在開發時以控制台的形式直接f5調試,發布時用命令以服務的形式部署。
還一個比較有用的特性是支持多實例的部署,這樣可以在一台機器上部署多個相對的服務。類似的工具有instsrv和srvany。
多實例有一個好處就是容災,當一個服務部署多份時,這樣其中任何一個服務實例掛了,剩余的可以繼續執行。
多實例可以是主備的方式,主掛了備服務才會執行。也可以以負載均衡的方式實現,多實例搶占進程鎖或分布式鎖,誰拿到誰執行。
先寫出具體步驟:
// 新建控制台應用程序
// 使用Nuget安裝Topshelf,選擇能用的最新版本
// 使用Nuget安裝NLog和NLog.config,選擇能用的最新版本,用於打印日志 Nlog需要配置文件,詳見NLog.config
// 初始化配置文件,創建AppConfigHelper類,繼承 ConfigurationSection (需要引用System.Configuration程序集)
// 完善App.Config配置文件,讀取App.Config配置文件,具體查看AppConfigHelper類
// 創建一個注冊服務類TopshelfRegistService,初始化Topshelf注冊
// 我們的目標很簡單,就是讓服務打印一個日志文件
// 編譯並生成項目,進入 bin\Debug 目錄下,找到xxx.exe 執行 install 命令,Windows 服務就誕生了
// 注意:如果出現需要以管理員身份啟動的提示,重新以管理員身份啟動 cmd
//接下來直接上代碼與截圖



卸載服務:

當我們啟動服務的時候,成功打印出了日志,表示一切成功

程序結構很簡單,如下圖所示:

接下來,我們直接上實現代碼,我會按照步驟依次給出:
1,Program主程序代碼
1 namespace ProcessPrintLogService 2 { 3 class Program 4 { 5 public static readonly Logger log = LogManager.GetCurrentClassLogger(); 6 private static readonly AppConfigHelper config = AppConfigHelper.Initity(); 7 static void Main(string[] args) 8 { 9 TopshelfRegistService.Regist(config, true); 10 } 11 } 12 }
2.AppConfigHelper類,用於讀取配置文件,使用配置文件的方式可以使你后期將該服務應用於多個應用程序
namespace ProcessPrintLogService { public class AppConfigHelper : ConfigurationSection { private static AppConfigHelper _AppConfig = null; private static readonly object LockThis = new object(); /// <summary> /// 獲取當前配置 獲取section節點的內容 /// 使用單例模式 /// </summary> /// <returns></returns> public static AppConfigHelper Initity() { if (_AppConfig == null) { lock (LockThis) { if (_AppConfig == null) { //獲取app.config文件中的section配置節點 _AppConfig = (AppConfigHelper)ConfigurationManager.GetSection("AppConfigHelper"); } } } return _AppConfig; } //創建一個AppConfigHelper節點 //屬性分別為:ServiceName、Desc 等.... //這里介紹一下屬性標簽:ConfigurationProperty 它可以在配置文件中根據屬性名獲取Value值 //可以參考文章https://www.cnblogs.com/liunlls/p/configuration.html /// <summary> /// 服務名稱 /// </summary> [ConfigurationProperty("ServiceName", IsRequired = true)] public string ServiceName { get { return base["ServiceName"].ToString(); } internal set { base["ServiceName"] = value; } } /// <summary> /// 描述 /// </summary> [ConfigurationProperty("Desc", IsRequired = true)] public string Description { get { return base["Desc"].ToString(); } internal set { base["Desc"] = value; } } } }
3.Topshelf組件注冊服務
namespace ProcessPrintLogService { /// <summary> /// Topshelf組件注冊服務 /// </summary> internal class TopshelfRegistService { /// <summary> /// 注冊入口 /// </summary> /// <param name="config">配置文件</param> /// <param name="isreg">是否注冊</param> public static void Regist(AppConfigHelper config, bool isreg = false) { //這里也可以使用HostFactory.Run()代替HostFactory.New() var host = HostFactory.New(x => { x.Service<QuartzHost>(s => { //通過 new QuartzHost() 構建一個服務實例 s.ConstructUsing(name => new QuartzHost()); //當服務啟動后執行什么 s.WhenStarted(tc => tc.Start()); //當服務停止后執行什么 s.WhenStopped(tc => tc.Stop()); //當服務暫停后執行什么 s.WhenPaused(w => w.Stop()); //當服務繼續后執行什么 s.WhenContinued(w => w.Start()); }); if (!isreg) return; //默認不注冊 //服務用本地系統賬號來運行 x.RunAsLocalSystem(); //服務的描述信息 x.SetDescription(config.Description); //服務的顯示名稱 x.SetDisplayName(config.ServiceName); //服務的名稱(最好不要包含空格或者有空格屬性的字符)Windows 服務名稱不能重復。 x.SetServiceName(config.ServiceName); }); host.Run(); //啟動服務 如果使用HostFactory.Run()則不需要該方法 } } /// <summary> /// 自定義服務 /// </summary> internal class QuartzHost { public readonly Logger log = LogManager.GetLogger("QuartzHost"); public QuartzHost() { var service = AppConfigHelper.Initity(); } //服務開始 public void Start() { try { Task.Run(() => { log.Info($"服務開始成功!"); }); } catch (Exception ex) { Task.Run(() => { log.Fatal(ex, $"服務開始失敗!錯誤信息:{0}", ex); }); throw; } } //服務停止 public void Stop() { Task.Run(() => { log.Trace("服務結束工作"); }); } } }
4.App.config配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration> <!--該節點一定要放在最上邊--> <configSections> <section name="AppConfigHelper" type="ProcessPrintLogService.AppConfigHelper,ProcessPrintLogService"/> </configSections> <!--TopSelf服務配置文件 --> <AppConfigHelper ServiceName="Process_PrintLogService" Desc="日志打印服務" /> <!--數據庫連接字符串 --> <connectionStrings> <add name="ConnectionString" connectionString="123123123"/> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> </configuration>
5.Nlog.config日志配置文件
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <targets> <!--type="File|Console" 屬性是設置日志輸出目標是"File"(文件)或者"Console"(控制台)--> <!--fileName="${basedir}/logs/${shortdate}/${level}/${callsite}.log" 設置日記記錄文件的路徑和名稱--> <!--layout="${longdate} ${level} ${callsite}:${message}" 設置日志輸出格式--> <target name="t1" type="File" fileName="${basedir}/logs/${shortdate}/${level} ${callsite}.log" layout="${longdate} ${level} ${callsite}:${message}" archiveAboveSize="3145728" archiveNumbering="Rolling" concurrentWrites="false" keepFileOpen="true" maxArchiveFiles ="20" /> <!--輸出至控制台--> <target name="t2" type="Console" layout="${longdate} ${level} ${callsite}:${message}" /> </targets> <rules> <!--如果填*,則表示所有的Logger都運用這個規則,將所有級別的日志信息都寫入到“t1”和“t2”這兩個目標里--> <logger name="*" writeTo="t1,t2"/> </rules> </nlog>
以上就是此次示例的全部代碼,到此你也許會有一個問題,就是我想定時執行我的任務?比如每天幾點執行,或者每幾分鍾執行一次等等,那我們該怎么做呢?
答案是使用:Quartz.net ,接下來我將會使用 Quartz.net 實現上述的定時任務。
參考文獻:
https://www.jianshu.com/p/f2365e7b439c
http://www.80iter.com/blog/1451523192435464/
https://www.itsvse.com/thread-7503-1-1.html?tdsourcetag=s_pctim_aiomsg
https://www.cnblogs.com/yanglang/p/7199913.html
