开发 Windows 服务应用
在 Windows 服务中托管 ASP.NET Core
如何:调试 Windows 服务应用程序
文档目录
.NET 文档
ASP.NET 文档
在 ASP.NET Core 中使用托管服务实现后台任务
ASP.NET Core 计时的后台任务
使用 IHostedService 和 BackgroundService 类在微服务中实现后台任务
1、Windows服务程序相关的命名空间
涉及到以下两个:System.ServiceProcess 和 System.Diagnostics。
2、Windows 服务开发框架(第三方组件)
Topshelf:一款非常好用的 Windows 服务开发框架
Install-Package Topshelf
Install-Package Topshelf.Log4Net
3、托管服务 后台任务
在 ASP.NET Core 中使用托管服务实现后台任务
使用 IHostedService 和 BackgroundService 类在微服务中实现后台任务
对于 ASP.NET Core 应用,将隐式添加 Microsoft.Extensions.Hosting 引用。
托管服务实现 IHostedService 接口。 该接口为主机托管的对象定义了两种方法:
● StartAsync(CancellationToken) – StartAsync 包含启动后台任务的逻辑。
● StopAsync(CancellationToken) – 主机正常关闭时触发。
● BackgroundService:是用于实现长时间运行的 IHostedService 的基类。
● 计时的后台任务:定时后台任务使用 System.Threading.Timer 类。 计时器触发任务的 DoWork 方法。 在 StopAsync 上禁用计时器,并在 Dispose 上处置服务容器时处置计时器
● 在后台任务中使用有作用域的服务
● 排队的后台任务:后台任务队列基于 QueueBackgroundWorkItem
● 如果后台任务与 HTTP (IWebHost) 无关,则应使用 IHost
●
*
*
*
4、创建服务项目
打开Visual Studio -> 文件 -> 新建 -> 项目 -> 已安装 -> Visual C# -> Windows 桌面 -> Windows 服务(.NET Framework)
5、文件说明
Program.cs:主程序入口
using System.ServiceProcess;
namespace MyWindowsService
{
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
}
}
Service1.cs:编写逻辑代码,F7进入代码页面(或 点击切换代码视图)
OnStart(string[] args):执行启动服务事件,一般采用线程方式执行方法,便于隔一段事件执行一回。
OnStop():执行停止服务事件。
using System;
using System.IO;
using System.ServiceProcess;
using System.Threading.Tasks;
namespace MyWindowsService
{
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
/// <summary>
/// 服务开启
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
Task.Factory.StartNew(Handle);
}
/// <summary>
/// 服务关闭
/// </summary>
protected override void OnStop()
{
}
/// <summary>
/// 服务暂停执行代码
/// </summary>
protected override void OnPause()
{
base.OnPause();
}
/// <summary>
/// 服务恢复执行代码
/// </summary>
protected override void OnContinue()
{
base.OnContinue();
}
/// <summary>
/// 系统即将关闭执行代码
/// </summary>
protected override void OnShutdown()
{
base.OnShutdown();
}
//需要定时执行的代码段
private void Handle()
{
while (true)
{
try
{
var path = AppDomain.CurrentDomain.BaseDirectory + "service.log";
var context = "MyWindowsService: Service Stoped " + DateTime.Now + "\n";
WriteLogs(path, context);
}
catch (Exception)
{
throw;
}
}
}
public void WriteLogs(string path, string context)
{
var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write);
var sw = new StreamWriter(fs);
sw.BaseStream.Seek(0, SeekOrigin.End);
sw.WriteLine(context);
sw.Flush();
sw.Close();
fs.Close();
}
}
}
6、创建服务安装程序
选中Service1.cs,右键-->查看设计器 ;在设计器中右键-->添加安装程序
7、安装、卸载服务
安装服务需要InstallUtil.exe,一般在电脑这个目录找到C:\Windows\Microsoft.NET\Framework64\v4.0.30319
【注意:在C:\Windows\Microsoft.NET\Framework目录下有很多类似版本,具体去哪个目录要看项目的运行环境,例 如果是.net framework2.0则需要输入 cd C:\Windows\Microsoft.NET\Framework\v2.0.50727】
dos窗口 -> cmd -> 确定
cd C:\Windows\Microsoft.NET\Framework64\v4.0.30319
安装服务: Installutil C:\Debug\你的服务名称.exe
卸载服务: Installutil /u C:\Debug\<yourproject>.exe
8、查看服务
我的电脑 -> 右键管理 -> 服务和应用程序 -> 服务 -> 找到服务名称
9、调试服务
首先确保服务已经安装成功,并且处于已启动
Visual Studio -> 调试 -> 附加到进程 -> 找到服务名称 -> 确定
---------【Topshelf】---------
---------【Topshelf】---------
---------【Topshelf】---------
---------【Topshelf】---------
---------【Topshelf】---------
1、管理 NuGet 程序包
Topshelf
NLog
2、Topshelf.ServiceConfigurators
void ConstructUsing(ServiceFactory<T> serviceFactory);
void WhenContinued(Func<T, HostControl, bool> @continue); //继续运行服务
void WhenCustomCommandReceived(Action<T, HostControl, int> customCommandReceived);
void WhenPaused(Func<T, HostControl, bool> pause); //暂停服务
void WhenPowerEvent(Func<T, HostControl, PowerEventArguments, bool> powerEvent);
void WhenSessionChanged(Action<T, HostControl, SessionChangedArguments> sessionChanged);
void WhenShutdown(Action<T, HostControl> shutdown); //关闭服务
void WhenStarted(Func<T, HostControl, bool> start); //启动服务
void WhenStopped(Func<T, HostControl, bool> stop); //停止服务
3、创建服务项目
打开Visual Studio -> 文件 -> 新建 -> 项目 -> 已安装 -> Visual C# -> Windows 桌面 -> 控制台应用(.NET Framework)
4、Program.cs:主程序入口
using Topshelf;
namespace TopshelfApp
{
class Program
{
static int Main(string[] args)
{
return (int)HostFactory.Run(x =>
{
#region 方式1
x.Service<SyncService>(s =>
{
s.ConstructUsing(name => new SyncService());
s.WhenStarted((tc, hostControl) => tc.OnStart(hostControl));
s.WhenStopped((tc, hostControl) => tc.OnStop());
});
#endregion
#region 方式2
//x.Service<TopshelfService>(s =>
//{
// s.ConstructUsing(b => new TopshelfService());
// s.WhenStarted(o => o.Start());
// s.WhenStopped(o => o.Stop());
//});
#endregion
x.RunAsLocalSystem();
x.StartAutomatically();
x.SetDescription("Topshelf_Test_Sync_Service");
x.SetDisplayName("Topshelf_Test_Sync_Service");
x.SetServiceName("Topshelf_Test_Sync_Service");
});
}
}
}
5、SyncService.cs
using NLog;
using System;
using System.Configuration;
using System.Threading;
using System.Threading.Tasks;
using Topshelf;
namespace TopshelfApp
{
public class SyncService
{
Logger log = LogManager.GetCurrentClassLogger();
CancellationTokenSource tokenSource = new CancellationTokenSource();
SyncNewEmployeeData syncNewEmp;
SyncAssetUseData syncAssetUse;
public SyncService() { }
public bool OnStart(HostControl hostControl)
{
log.Info("Sync Service start...");
try
{
//<add key="SyncNewEmployeeTime" value="23:59" />
var syncNewEmpTime = ConfigurationManager.AppSettings["SyncNewEmployeeTime"];
syncNewEmp = new SyncNewEmployeeData(syncNewEmpTime);
syncNewEmp.Start();
//<add key="SyncAssetUseTime" value="01:59" />
var syncAssetUseTime = ConfigurationManager.AppSettings["SyncAssetUseTime"];
syncAssetUse = new SyncAssetUseData(syncAssetUseTime);
syncAssetUse.Start();
// Do something continue...
}
catch (Exception ex)
{
log.Error("Error while trying to initiate services on start methods, detail: " + ex.ToString());
return false;
}
return true;
}
public bool OnStop()
{
tokenSource.Cancel();
try
{
Task.WaitAll(syncNewEmp.Stop(), syncAssetUse.Stop());
log.Info("Sync Service stopped.");
}
catch (Exception ex)
{
log.Error("Error occurred while stopping sync service, detail: " + ex.ToString());
throw;
}
return true;
}
}
}
6、TopshelfService
using System;
using System.Timers;
namespace TopshelfApp
{
public sealed class TopshelfService
{
private readonly Timer _timer;
public TopshelfService()
{
_timer = new Timer(1000) { AutoReset = true };
_timer.Elapsed += (sender, eventArgs) => Console.WriteLine("当前时间:{0}", DateTime.Now);
}
public void Start()
{
//服务逻辑
_timer.Start();
}
public void Stop()
{
_timer.Stop();
}
}
}
7、SyncNewEmployeeData.cs
using NLog;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace TopshelfApp
{
public class SyncNewEmployeeData
{
Logger log = LogManager.GetCurrentClassLogger();
CancellationTokenSource tokenSource = new CancellationTokenSource();
private Task _t;
private bool _running = true;
private string _runningTime;
public SyncNewEmployeeData(string runningTime)
{
//this running time MUST BE in HH:mm format, e.g. 23:59
_runningTime = runningTime;
}
public void Start()
{
_running = true;
log.Info("Sync new employee data service started.");
Run();
}
public Task Stop()
{
_running = false;
tokenSource.Cancel();
log.Info("Sync new employee data service stopped.");
return _t;
}
private void Run()
{
log.Debug("Initiate thread for Synchronizing new employee data.");
_t = Task.Factory.StartNew(async () =>
{
while (_running)
{
try
{
var dt = DateTime.ParseExact(_runningTime, "HH:mm", CultureInfo.InvariantCulture);
if (DateTime.Now > dt)
{
var gap = dt.AddDays(1) - DateTime.Now;
await Task.Delay(gap, tokenSource.Token);
}
else
{
var gap = dt - DateTime.Now;
await Task.Delay(gap, tokenSource.Token);
}
// Do something ...
//new EmployeeService().GetHrxUserInfo(token);
}
catch (Exception ex)
{
log.Error("FAILED to sync ... data, detail: " + ex.Message);
}
log.Debug($"Sync ... data service status is {(_running ? "running" : "stopping")}");
}
});
}
}
}
8、SyncAssetUseData.cs
using NLog;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace TopshelfApp
{
public class SyncAssetUseData
{
Logger log = LogManager.GetCurrentClassLogger();
CancellationTokenSource tokenSource = new CancellationTokenSource();
private Task _t;
private bool _running = true;
private string _runningTime;
public SyncAssetUseData(string runningTime)
{
//this running time MUST BE in HH:mm format, e.g. 23:59
_runningTime = runningTime;
}
public void Start()
{
_running = true;
log.Info("Sync assetUse data service started.");
Run();
}
public Task Stop()
{
_running = false;
tokenSource.Cancel();
log.Info("Sync assetUse data service stopped.");
return _t;
}
private void Run()
{
log.Debug("Initiate thread for Synchronizing assetUse data.");
_t = Task.Factory.StartNew(async () =>
{
while (_running)
{
try
{
var dt = DateTime.ParseExact(_runningTime, "HH:mm", CultureInfo.InvariantCulture);
if (DateTime.Now > dt)
{
var gap = dt.AddDays(1) - DateTime.Now;
await Task.Delay(gap, tokenSource.Token);
}
else
{
var gap = dt - DateTime.Now;
await Task.Delay(gap, tokenSource.Token);
}
// Do something ...
//new AssetUseRevertService().EmployeeUse();
}
catch (Exception ex)
{
log.Error("FAILED to sync ... data, detail: " + ex.Message);
}
log.Debug($"Sync ... data service status is {(_running ? "running" : "stopping")}");
}
});
}
}
}
*、创建一个.net core 控制台项目
*、nuget安装:System.ServiceProcess.ServiceController
*、添加 Windows Service 项【Windows 服务】
*、NotificationService.cs 代码如下
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
namespace Test.WinService
{
partial class NotificationService : ServiceBase
{
public NotificationService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
// TODO: 在此处添加代码以启动服务。
}
protected override void OnStop()
{
// TODO: 在此处添加代码以执行停止服务所需的关闭操作。
}
}
}
*、Program.cs 代码如下
using System;
using System.ServiceProcess;
namespace Test.WinService
{
class Program
{
static void Main(string[] args)
{
ServiceBase[] services = new ServiceBase[]
{
new NotificationService()
};
ServiceBase.Run(services);
}
}
}
*、BackgroundService Web API
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WebAPI
{
public class TestHostedService : BackgroundService
{
private readonly ITestRepository _testRepo;
private readonly ILogger<HuobiHostedService> _logger;
private Timer _timer;
public TestHostedService(ILogger<HuobiHostedService> logger, ITestRepository testRepo)
{
_testRepo = testRepo;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
while (!stoppingToken.IsCancellationRequested)
{
int minute = DateTime.Now.Minute;
if (minute % 15 == 0)
{
_logger.LogInformation($"Ln 50 {minute}");
}
await Task.Delay(60000, stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken).ContinueWith(tsk=>
{
DoWork();
});
}
if (!stoppingToken.IsCancellationRequested)
{
await Task.Run(() =>
{
// 会报错
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
});
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Huobi Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
private void DoWork(object state)
{
int minute = DateTime.Now.Minute;
if (minute % 15 == 0)
{
_logger.LogInformation($"Ln 50 {minute}");
}
}
}
}
*
*
*