是否有更简单的方法来单步执行代码,而不是通过Windows服务控制管理器启动服务,然后将调试器附加到线程?这有点麻烦,我想知道是否有一个更简单的方法.
如果我想快速调试服务,我只需要插入一个Debugger.Break()
.当达到那条线时,它会让我回到VS. 完成后别忘了删除该行.
更新:作为#if DEBUG
编译指示的替代方法,您还可以使用Conditional("DEBUG_SERVICE")
属性.
[Conditional("DEBUG_SERVICE")] private static void DebugMode() { Debugger.Break(); }
在你的OnStart
,只需调用此方法:
public override void OnStart() { DebugMode(); /* ... do the rest */ }
在那里,代码将仅在Debug构建期间启用.在使用它时,为服务调试创建单独的Build Configuration可能很有用.
我还认为有一个单独的"版本"用于正常执行,作为服务是要走的路,但是真的需要专门为此目的专门设置一个命令行开关吗?
你不能这样做:
public static int Main(string[] args) { if (!Environment.UserInteractive) { // Startup as service. } else { // Startup as application } }
这将有"好处",您可以通过双击启动您的应用程序(好的,如果您确实需要),并且您可以直接点击F5Visual Studio(无需修改项目设置以包含该/console
选项).
从技术上讲,Environment.UserInteractive
检查是否WSF_VISIBLE
为当前窗口站设置了标志,但是false
除了作为(非交互式)服务运行之外,还有其他原因可以返回吗?
几周前,当我找到一个新的服务项目时,我找到了这个帖子.虽然有很多很棒的建议,但我仍然没有找到我想要的解决方案:可以在不对服务类进行任何修改的情况下调用服务类OnStart
和OnStop
方法.
我提出的解决方案使用了Environment.Interactive
选择运行模式,正如本文的其他答案所示.
static void Main() { ServiceBase[] servicesToRun; servicesToRun = new ServiceBase[] { new MyService() }; if (Environment.UserInteractive) { RunInteractive(servicesToRun); } else { ServiceBase.Run(servicesToRun); } }
该RunInteractive
助手使用反射来调用保护OnStart
和OnStop
方法:
static void RunInteractive(ServiceBase[] servicesToRun) { Console.WriteLine("Services running in interactive mode."); Console.WriteLine(); MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart", BindingFlags.Instance | BindingFlags.NonPublic); foreach (ServiceBase service in servicesToRun) { Console.Write("Starting {0}...", service.ServiceName); onStartMethod.Invoke(service, new object[] { new string[] { } }); Console.Write("Started"); } Console.WriteLine(); Console.WriteLine(); Console.WriteLine( "Press any key to stop the services and end the process..."); Console.ReadKey(); Console.WriteLine(); MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop", BindingFlags.Instance | BindingFlags.NonPublic); foreach (ServiceBase service in servicesToRun) { Console.Write("Stopping {0}...", service.ServiceName); onStopMethod.Invoke(service, null); Console.WriteLine("Stopped"); } Console.WriteLine("All services stopped."); // Keep the console alive for a second to allow the user to see the message. Thread.Sleep(1000); }
这是所需的所有代码,但我还编写了演练解释.
有时,分析服务启动期间发生的事情非常重要.附加到该过程在这里没有帮助,因为在服务启动时您没有足够快速地附加调试器.
简短的回答是,我使用以下4行代码来执行此操作:
#if DEBUG base.RequestAdditionalTime(600000); // 600*1000ms = 10 minutes timeout Debugger.Launch(); // launch and attach debugger #endif
这些插入到OnStart
服务的方法中,如下所示:
protected override void OnStart(string[] args) { #if DEBUG base.RequestAdditionalTime(600000); // 10 minutes timeout for startup Debugger.Launch(); // launch and attach debugger #endif MyInitOnstart(); // my individual initialization code for the service // allow the base class to perform any work it needs to do base.OnStart(args); }
对于那些之前没有做过的人,我在下面列出了详细的提示,因为你很容易陷入困境.以下提示涉及Windows 7x64和Visual Studio 2010 Team Edition,但也应对其他环境有效.
要点:以"手动"模式部署服务(使用InstallUtil
VS命令提示符中的实用程序或运行您准备的服务安装程序项目).在启动服务之前打开Visual Studio 并加载包含服务源代码的解决方案 - 在Visual Studio中根据需要设置其他断点 - 然后通过服务控制面板启动服务.
由于Debugger.Launch
代码,这将导致对话框" Servicename.exe中出现未处理的Microsoft .NET Framework异常".出现.点击屏幕截图所示: Yes, debug Servicename.exe
之后,特别是在Windows 7中,UAC可能会提示您输入管理员凭据.输入它们并继续Yes:
之后,将出现众所周知的Visual Studio即时调试器窗口.它会询问您是否要使用delected调试器进行调试.在单击之前Yes,选择您不想打开新实例(第二个选项) - 这里的新实例没有用处,因为源代码不会显示.因此,您选择之前打开的Visual Studio实例:
点击之后Yes,经过一段时间的Visual Studio将显示在右边线的黄色箭头,其中Debugger.Launch
语句,你能调试代码(方法MyInitOnStart
,其中包含你的初始化).
按下将F5立即继续执行,直到达到您准备的下一个断点.
提示:要使服务保持运行,请选择Debug - > Detach all.这允许您在正确启动服务后运行与服务通信的客户端,并且您已完成调试启动代码.如果按Shift+F5(停止调试),这将终止服务.您应该使用服务控制面板来停止它,而不是这样做.
需要注意的是
如果构建版本,则会自动删除调试代码并且服务正常运行.
我正在使用Debugger.Launch()
,它启动并附加调试器.我也测试Debugger.Break()
了,但是没有用,因为在启动服务时没有连接调试器(导致"错误1067:进程意外终止.").
RequestAdditionalTime
为服务启动设置更长的超时(它不会延迟代码本身,但会立即继续使用该Debugger.Launch
语句).否则,启动服务的默认超时时间太短,如果您没有base.Onstart(args)
从调试器中快速调用,则启动服务会失败.实际上,超时10分钟可以避免在调试器启动后立即看到" 服务没有响应..."消息.
一旦习惯了,这种方法非常简单,因为它只需要在现有服务代码中添加4行,这样您就可以快速获得控制和调试.
我通常做的是将服务的逻辑封装在一个单独的类中,并从"runner"类开始.此运行器类可以是实际服务,也可以只是控制台应用程序.所以你的解决方案有(至少)3个项目:
/ConsoleRunner /.... /ServiceRunner /.... /ApplicationLogic /....
这部由Fabio Scopel撰写的YouTube视频解释了如何很好地调试Windows服务......实际的方法是在视频中的4:45开始...
这是代码中解释的代码...在Program.cs文件中,添加Debug部分的内容......
namespace YourNamespace { static class Program { ////// The main entry point for the application. /// static void Main() { #if DEBUG Service1 myService = new Service1(); myService.OnDebug(); System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); #else ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); #endif } } }
在Service1.cs文件中,添加OnDebug()方法...
public Service1() { InitializeComponent(); } public void OnDebug() { OnStart(null); } protected override void OnStart(string[] args) { // your code to do something } protected override void OnStop() { }
这个怎么运作
基本上你必须创建一个public void OnDebug()
调用OnStart(string[] args)
它,因为它受到保护,不能在外面访问.该void Main()
程序添加了#if
预处理器#DEBUG
.
Visual Studio定义DEBUG
项目是否在调试模式下编译.这将允许调试部分(如下)在条件为真时执行
Service1 myService = new Service1(); myService.OnDebug(); System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
并且它将像控制台应用程序一样运行,一旦事情变好,您可以更改模式Release
,常规else
部分将触发逻辑
UPDATE
这种方法是迄今为止最简单的方法:
http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx
我将下面的原始答案留给后人.
我的服务往往有一个封装Timer的类,因为我希望服务定期检查是否有任何工作要做.
我们新上课并在服务启动期间调用StartEventLoop().(这个类也可以很容易地从控制台应用程序中使用.)
这种设计的好处是,设置Timer的参数可以用来在服务实际开始工作之前有一个延迟,这样你就有时间手动连接一个调试器.
ps 如何手动将调试器附加到正在运行的进程...?
using System; using System.Threading; using System.Configuration; public class ServiceEventHandler { Timer _timer; public ServiceEventHandler() { // get configuration etc. _timer = new Timer( new TimerCallback(EventTimerCallback) , null , Timeout.Infinite , Timeout.Infinite); } private void EventTimerCallback(object state) { // do something } public void StartEventLoop() { // wait a minute, then run every 30 minutes _timer.Change(TimeSpan.Parse("00:01:00"), TimeSpan.Parse("00:30:00"); } }
此外,我曾经做过以下操作(在前面的答案中已经提到但是使用条件编译器[#if]标志来帮助避免它在Release版本中触发).
我不这样做了,因为有时我们会忘记在Release中构建并在运行客户端演示的应用程序中进行调试器中断(令人尴尬!).
#if DEBUG if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); } #endif
static void Main() { #if DEBUG // Run as interactive exe in debug mode to allow easy // debugging. var service = new MyService(); service.OnStart(null); // Sleep the main thread indefinitely while the service code // runs in .OnStart Thread.Sleep(Timeout.Infinite); #else // Run normally as service in release mode. ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[]{ new MyService() }; ServiceBase.Run(ServicesToRun); #endif }
您还可以通过命令提示符(sc.exe)启动该服务.
就个人而言,我会在调试阶段将代码作为独立程序运行,并且当大多数错误被解决时,将更改为作为服务运行.
我以前做的是有一个命令行开关,它可以作为服务或常规应用程序启动程序.然后,在我的IDE中,我会设置开关,以便我可以单步执行代码.
使用某些语言,您实际上可以检测它是否在IDE中运行,并自动执行此切换.
你用的是哪种语言?
使用TopShelf库.
创建一个控制台应用程序,然后在Main中配置设置
class Program { static void Main(string[] args) { HostFactory.Run(x => { // setup service start and stop. x.Service(s => { s.ConstructUsing(name => new Controller()); s.WhenStarted(controller => controller.Start()); s.WhenStopped(controller => controller.Stop()); }); // setup recovery here x.EnableServiceRecovery(rc => { rc.RestartService(delayInMinutes: 0); rc.SetResetPeriod(days: 0); }); x.RunAsLocalSystem(); }); } } public class Controller { public void Start() { } public void Stop() { } }
要调试您的服务,只需在Visual Studio中点击F5即可.
要安装服务,请键入cmd"console.exe install"
然后,您可以在Windows服务管理器中启动和停止服务.
我认为这取决于您使用的操作系统,因为会话之间的分离,Vista很难连接到服务.
我过去使用的两个选项是:
使用GFlags(在Windows调试工具中)为进程设置永久调试器.这存在于"映像文件执行选项"注册表项中,非常有用.我认为您需要调整服务设置以启用"与桌面交互".我将它用于所有类型的调试,而不仅仅是服务.
另一个选择是将代码分开一点,以便服务部分可以与正常的应用程序启动互换.这样,您可以使用简单的命令行标志,并作为进程(而不是服务)启动,这使得调试更容易.
希望这可以帮助.
当我编写服务时,我将所有服务逻辑放在一个dll项目中并创建两个调用此dll的"主机",一个是Windows服务,另一个是命令行应用程序.
我使用命令行应用程序进行调试,并将调试器附加到实际服务,仅用于我无法在命令行应用程序中重现的错误.
我使用这种方法只记得你必须在真实服务中运行时测试所有代码,而命令行工具是一个很好的调试辅助工具,它是一个不同的环境,它的行为与真实服务完全不同.
我希望能够调试我的服务的每个方面,包括OnStart()中的任何初始化,同时仍然在SCM的框架内以完整的服务行为执行它...没有"控制台"或"应用程序"模式.
我这样做是通过在同一个项目中创建第二个服务来用于调试.调试服务,像往常一样启动(即在服务MMC插件中),创建服务主机进程.即使您尚未启动实际服务,这也为您提供了附加调试器的过程.将调试器附加到进程后,启动实际服务,您可以在服务生命周期的任何地方(包括OnStart())进入.
因为它需要非常少的代码入侵,所以调试服务可以轻松地包含在您的服务设置项目中,并且可以通过注释掉一行代码并删除单个项目安装程序来轻松地从生产版本中删除.
细节:
1)假设您正在实施MyService
,也创建MyServiceDebug
.像这样添加到ServiceBase
数组Program.cs
:
////// The main entry point for the application. /// static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new MyService(), new MyServiceDebug() }; ServiceBase.Run(ServicesToRun); }
2)将实际服务和调试服务添加到服务项目的项目安装程序:
将服务项目输出添加到服务的安装项目时,将包括这两种服务(实际和调试).安装后,两个服务都将出现在service.msc MMC插件中.
3)在MMC中启动调试服务.
4)在Visual Studio中,将调试器附加到调试服务启动的进程.
5)启动真实服务并享受调试.