【发布时间】:2023-03-12 23:20:01
【问题描述】:
有没有比通过 Windows 服务控制管理器启动服务然后将调试器附加到线程更简单的方法来单步执行代码?这有点麻烦,我想知道是否有更直接的方法。
【问题讨论】:
-
我创建了这张用户语音票。考虑为它投票:visualstudio.uservoice.com/forums/121579-visual-studio-ide/…
标签: c# debugging windows-services
有没有比通过 Windows 服务控制管理器启动服务然后将调试器附加到线程更简单的方法来单步执行代码?这有点麻烦,我想知道是否有更直接的方法。
【问题讨论】:
标签: c# debugging windows-services
如果我想快速调试服务,我只需在其中输入Debugger.Break()。当到达那条线时,它会让我回到 VS。完成后不要忘记删除该行。
更新:作为#if DEBUG pragmas 的替代方法,您还可以使用Conditional("DEBUG_SERVICE") 属性。
[Conditional("DEBUG_SERVICE")]
private static void DebugMode()
{
Debugger.Break();
}
在您的OnStart 上,只需调用此方法:
public override void OnStart()
{
DebugMode();
/* ... do the rest */
}
在那里,代码只会在调试构建期间启用。当您使用它时,为服务调试创建一个单独的构建配置可能会很有用。
【讨论】:
我也认为有一个单独的“版本”用于正常执行和作为服务是要走的路,但是否真的需要为此目的专用一个单独的命令行开关?
你就不能这样做吗:
public static int Main(string[] args)
{
if (!Environment.UserInteractive)
{
// Startup as service.
}
else
{
// Startup as application
}
}
这将有“好处”,您可以通过双击启动您的应用程序(好的,如果您真的需要的话)并且您可以在 Visual Studio 中点击 F5 (无需修改项目设置以包含 /console 选项)。
从技术上讲,Environment.UserInteractive 检查是否为当前窗口站设置了WSF_VISIBLE 标志,但除了作为(非交互式)服务运行之外,是否还有其他原因会返回false ?
【讨论】:
System.Diagnostics.Debugger.IsAttached 而不是Environment.UserInteractive。跨度>
几周前,当我建立一个新的服务项目时,我发现了这篇文章。虽然有很多很好的建议,但我仍然没有找到我想要的解决方案:无需对服务类进行任何修改即可调用服务类的 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);
}
这是所需的所有代码,但我还写了walkthrough 并附有解释。
【讨论】:
walk through 所示)是确保在尝试编译和运行之前进入项目的属性并将输出类型更改为Console Application。在Project Properties -> Application -> Output type -> Console Application 找到它。此外,为了让这对我正常工作,我最终不得不使用start 命令运行应用程序。例如:C:\"my app name.exe" -service 对我不起作用。相反,我使用了C:\start /wait "" "my app name.exe" -service
有时分析在服务启动期间发生的情况很重要。附加到进程在这里没有帮助,因为在服务启动时附加调试器的速度不够快启动。
简短的回答是,我使用以下 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,但也应适用于其他环境。
重要提示:以“手动”模式部署服务(使用 VS 命令提示符下的 InstallUtil 实用程序或运行您准备好的服务安装程序项目) . 在启动服务之前打开 Visual Studio 并加载包含服务源代码的解决方案 - 根据需要在 Visual Studio 中设置额外的断点 - 然后通过服务控制面板启动服务。
由于Debugger.Launch 代码,这将导致对话框“Servicename.exe 中出现未处理的 Microsoft .NET Framework 异常。”出现。点击是的,调试Servicename.exe如截图所示:
之后,尤其是在 Windows 7 UAC 中,可能会提示您输入管理员凭据。输入它们并继续Yes:
之后,众所周知的 Visual Studio 即时调试器窗口 会出现。它会询问您是否要使用 delected 调试器进行调试。 在您点击是之前,选择您不想打开新实例(第二个选项) - 新实例没有帮助在这里,因为不会显示源代码。因此,您选择之前打开的 Visual Studio 实例:
点击Yes后,一段时间后,Visual Studio 将在Debugger.Launch 语句所在的行显示黄色箭头,您就可以进行调试了您的代码(方法MyInitOnStart,其中包含您的初始化)。
按下 F5 会立即继续执行,直到到达您准备的下一个断点。
提示:要保持服务运行,请选择Debug -> Detach all。这允许您在服务正确启动并完成调试启动代码后运行与服务通信的客户端。如果按Shift+F5(停止调试),这将终止服务。而不是这样做,您应该使用服务控制面板来停止它。
注意
如果你构建一个Release,那么调试代码会被自动移除并且服务正常运行。
我正在使用 Debugger.Launch(),它启动并附加调试器。我也测试了 Debugger.Break(),它不起作用,因为在启动服务时还没有附加调试器(导致 “错误 1067 :进程意外终止。”)。
RequestAdditionalTime 设置更长的服务启动超时(它不是延迟代码本身,而是将立即继续Debugger.Launch 语句)。否则,启动服务的默认超时时间太短,如果您没有从调试器中足够快地调用base.Onstart(args),则启动服务会失败。实际上,10 分钟的超时可避免您在调试器启动后立即看到消息“服务没有响应...”。
一旦你习惯了,这个方法就非常简单,因为它只需要你在现有的服务代码中添加4行,让你快速获得控制和调试。
【讨论】:
base.RequestAdditionalTime(600000) 在该时间段内未调用base.OnStart(args),则base.RequestAdditionalTime(600000) 将阻止服务控制终止服务10 分钟)。除此之外,我记得如果您在一段时间后不输入管理员凭据,UAC 也会中止(我不知道确切的秒数,但我认为您必须在一分钟内输入,否则 UAC 中止) ,这将终止调试会话。
我通常做的是将服务的逻辑封装在一个单独的类中,并从一个“跑步者”类开始。这个运行器类可以是实际的服务或只是一个控制台应用程序。所以你的解决方案有(至少)3个项目:
/ConsoleRunner
/....
/ServiceRunner
/....
/ApplicationLogic
/....
【讨论】:
这个YouTube video by Fabio Scopel 解释了如何很好地调试 Windows 服务...实际操作方法从视频中的 4:45 开始...
这是视频中解释的代码...在您的 Program.cs 文件中,为“调试”部分添加内容...
namespace YourNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
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()
{
}
它是如何工作的
基本上,您必须创建一个调用OnStart(string[] args) 的public void OnDebug(),因为它受到保护并且无法在外部访问。 void Main() 程序与#if 预处理器和#DEBUG 一起添加。
Visual Studio 定义DEBUG 如果项目在调试模式下编译。这将允许调试部分(如下)在条件为真时执行
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
它会像控制台应用程序一样运行,一旦一切正常,您可以更改模式Release,常规的else 部分将触发逻辑
【讨论】:
更新
这种方法是迄今为止最简单的:
http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx
我将我的原始答案留在下面以供后代使用。
我的服务往往有一个封装了 Timer 的类,因为我希望该服务定期检查是否有任何工作要做。
我们新建类并在服务启动期间调用 StartEventLoop()。 (这个类也可以很容易地从控制台应用程序中使用。)
这种设计的好处是,您设置 Timer 时使用的参数可用于在服务实际开始工作之前有一个延迟,以便您有时间手动附加调试器。
附言How to attach the debugger manually 到正在运行的进程...?
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 中构建并在客户端演示上运行的应用程序中出现调试器中断(尴尬!)。
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
#endif
【讨论】:
// do something 需要超过 30 分钟才能完成时会发生什么?
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
}
【讨论】:
OnStart 是protected 并且不能修改访问级别:(
您也可以通过命令提示符(sc.exe)启动服务。
就我个人而言,我会在调试阶段将代码作为独立程序运行,当大多数错误解决后,改为作为服务运行。
【讨论】:
我以前做的是有一个命令行开关,它可以将程序作为服务或常规应用程序启动。然后,在我的 IDE 中,我将设置开关,以便我可以单步执行我的代码。
对于某些语言,您实际上可以检测它是否在 IDE 中运行,并自动执行此切换。
你使用什么语言?
【讨论】:
使用TopShelf 库。
创建一个控制台应用程序,然后在您的 Main 中配置设置
class Program
{
static void Main(string[] args)
{
HostFactory.Run(x =>
{
// setup service start and stop.
x.Service<Controller>(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 更难附加到服务。
我过去使用的两个选项是:
希望这会有所帮助。
【讨论】:
我希望能够调试我的服务的各个方面,包括 OnStart() 中的任何初始化,同时仍然在 SCM 框架内以完整的服务行为执行它...没有“控制台”或“应用程序”模式.
我通过在同一个项目中创建第二个服务来进行调试。调试服务在正常启动时(即在服务 MMC 插件中)创建服务主机进程。即使您尚未启动真正的服务,这也为您提供了一个附加调试器的过程。将调试器附加到进程后,启动你的真实服务,你可以在服务生命周期的任何地方闯入它,包括 OnStart()。
因为它需要极少的代码侵入,调试服务可以轻松地包含在您的服务设置项目中,并且可以通过注释掉一行代码并删除单个项目安装程序轻松地从您的生产版本中删除。
详情:
1) 假设您正在实施MyService,同时创建MyServiceDebug。将两者都添加到 Program.cs 中的 ServiceBase 数组中,如下所示:
/// <summary>
/// The main entry point for the application.
/// </summary>
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) 启动真正的服务,享受调试。
【讨论】:
当我编写服务时,我将所有服务逻辑放在一个 dll 项目中,并创建两个调用该 dll 的“主机”,一个是 Windows 服务,另一个是命令行应用程序。
我使用命令行应用程序进行调试,并将调试器附加到实际服务中,仅针对我无法在命令行应用程序中重现的错误。
如果你使用这种方法,请记住你必须在真实服务中运行时测试所有代码,而命令行工具是一个很好的调试辅助工具,它是一个不同的环境,它的行为与真实服务不完全相同.
【讨论】:
在开发和调试 Windows 服务时,我通常通过添加 /console 启动参数并检查它来将其作为控制台应用程序运行。让生活更轻松。
static void Main(string[] args) {
if (Console.In != StreamReader.Null) {
if (args.Length > 0 && args[0] == "/console") {
// Start your service work.
}
}
}
【讨论】:
第一行的Debugger.Break()怎么样?
【讨论】:
为了调试 Windows 服务,我结合了 GFlags 和一个由 regedit 创建的 .reg 文件。
或者保存以下 sn-ps 并将 servicename.exe 替换为所需的可执行文件名称。
debugon.reg:
Windows 注册表编辑器版本 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\servicename.exe] “全球标志”=“0x00000000” "调试器"="vsjitdebugger.exe"
debugoff.reg:
Windows 注册表编辑器版本 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\servicename.exe] "GlobalFlag"="0x00000000"
【讨论】:
这是我用来测试服务的简单方法,没有任何额外的“调试”方法和集成的 VS 单元测试。
[TestMethod]
public void TestMyService()
{
MyService fs = new MyService();
var OnStart = fs.GetType().BaseType.GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
OnStart.Invoke(fs, new object[] { null });
}
// As an extension method
public static void Start(this ServiceBase service, List<string> parameters)
{
string[] par = parameters == null ? null : parameters.ToArray();
var OnStart = service.GetType().GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
OnStart.Invoke(service, new object[] { par });
}
【讨论】:
对于常规的小型编程,我做了一个非常简单的技巧来轻松调试我的服务:
在服务启动时,我检查命令行参数“/debug”。如果使用此参数调用服务,我不会进行通常的服务启动,而是启动所有侦听器并仅显示消息框“调试中,按确定结束”。
因此,如果我的服务以通常的方式启动,它将作为服务启动,如果使用命令行参数 /debug 启动,它将像普通程序一样运行。
在VS中我只添加/debug作为调试参数,直接启动服务程序。
这样我可以轻松调试大多数小问题。当然,有些东西仍然需要作为服务进行调试,但是对于 99% 来说这已经足够了。
【讨论】:
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
【讨论】:
我使用了 JOP 答案的变体。使用命令行参数,您可以在 IDE 中使用项目属性或通过 Windows 服务管理器设置调试模式。
protected override void OnStart(string[] args)
{
if (args.Contains<string>("DEBUG_SERVICE"))
{
Debugger.Break();
}
...
}
【讨论】:
对于现有 Windows 服务程序的故障排除,请按照其他人的建议使用“Debugger.Break()”。
对于新的 Windows 服务程序,我建议使用 James Michael Hare 的方法http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template-redux.aspx
【讨论】:
只需将调试器午餐放在任何地方并在启动时附加 Visualstudio
#if DEBUG
Debugger.Launch();
#endif
您还需要以管理员身份启动 VS,并且您需要允许不同用户自动调试进程(如 here 所述):
reg add "HKCR\AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f
【讨论】:
使用 Windows 服务模板 C# 项目新建服务应用https://github.com/HarpyWar/windows-service-template
自动检测到控制台/服务模式,自动安装/卸载您的服务以及几个最常用的功能。
【讨论】:
static class Program
{
static void Main()
{
#if DEBUG
// TODO: Add code to start application here
// //If the mode is in debugging
// //create a new service instance
Service1 myService = new Service1();
// //call the start method - this will start the Timer.
myService.Start();
// //Set the Thread to sleep
Thread.Sleep(300000);
// //Call the Stop method-this will stop the Timer.
myService.Stop();
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
#endif
}
}
【讨论】:
您有两个选项可以进行调试。
请参考我为该主题创建的 THIS 博客文章。
【讨论】:
粘贴
Debugger.Break();
代码中的任何位置。
例如,
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
private static void Main()
{
Debugger.Break();
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
}
当你运行你的程序时它会点击Debugger.Break();。
【讨论】:
最好的选择是使用“System.Diagnostics”命名空间。
将您的代码包含在调试模式和发布模式的 if else 块中,如下所示,以在 Visual Studio 中的调试和发布模式之间切换,
#if DEBUG // for debug mode
**Debugger.Launch();** //debugger will hit here
foreach (var job in JobFactory.GetJobs())
{
//do something
}
#else // for release mode
**Debugger.Launch();** //debugger will hit here
// write code here to do something in Release mode.
#endif
【讨论】: