我目前正在为可以在控制台中运行的服务编写一些引导代码.它本质上归结为调用OnStart()方法而不是使用ServiceBase来启动和停止服务(因为如果它没有作为服务安装并且使调试成为一场噩梦,它就不会运行应用程序).
现在我使用Debugger.IsAttached来确定我是否应该使用ServiceBase.Run或[service] .OnStart,但我知道这不是最好的主意,因为有些时候最终用户想要在控制台中运行服务(看看输出等实时).
关于如何确定Windows服务控制器是否启动"我",或者用户是否在控制台中启动"我"的任何想法?Apparantly Environment.IsUserInteractive不是答案.我想过使用命令行args,但这看起来很"脏".
我总是可以看到围绕ServiceBase.Run的try-catch语句,但这看起来很脏.编辑:尝试捕获不起作用.
我有一个解决方案:把它放在这里给所有其他感兴趣的堆叠器:
public void Run() { if (Debugger.IsAttached || Environment.GetCommandLineArgs().Contains("-console")) { RunAllServices(); } else { try { string temp = Console.Title; ServiceBase.Run((ServiceBase[])ComponentsToRun); } catch { RunAllServices(); } } } // void Run private void RunAllServices() { foreach (ConsoleService component in ComponentsToRun) { component.Start(); } WaitForCTRLC(); foreach (ConsoleService component in ComponentsToRun) { component.Stop(); } }
编辑:在StackOverflow上有另一个问题,那个人有环境问题.CurrentDirectory是"C:\ Windows\System32"看起来可能是答案.我今天要考试.
另一种解决方法..因此可以作为WinForm或Windows服务运行
var backend = new Backend(); if (Environment.UserInteractive) { backend.OnStart(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Fronend(backend)); backend.OnStop(); } else { var ServicesToRun = new ServiceBase[] {backend}; ServiceBase.Run(ServicesToRun); }
我通常将Windows服务标记为控制台应用程序,它将命令行参数"-console"作为控制台运行,否则它作为服务运行.要调试你只需将项目选项中的命令行参数设置为"-console",然后就可以了!
这使得调试变得简单易行,这意味着默认情况下应用程序可以作为服务运行,这正是您所需要的.
像Ash一样,我将所有实际的处理代码写在一个单独的类库程序集中,然后由windows服务可执行文件和控制台应用程序引用.
但是,有时候知道类库是否在服务可执行文件或控制台应用程序的上下文中运行是有用的.我这样做的方式是反映托管应用程序的基类.(抱歉VB,但我想以下可能很容易被c#化):
Public Class ExecutionContext
'''
''' Gets a value indicating whether the application is a windows service.
'''
'''
''' true if this instance is service; otherwise, false .
'''
Public Shared ReadOnly Property IsService() As Boolean
Get
' Determining whether or not the host application is a service is
' an expensive operation (it uses reflection), so we cache the
' result of the first call to this method so that we don't have to
' recalculate it every call.
' If we have not already determined whether or not the application
' is running as a service...
If IsNothing(_isService) Then
' Get details of the host assembly.
Dim entryAssembly As Reflection.Assembly = Reflection.Assembly.GetEntryAssembly
' Get the method that was called to enter the host assembly.
Dim entryPoint As System.Reflection.MethodInfo = entryAssembly.EntryPoint
' If the base type of the host assembly inherits from the
' "ServiceBase" class, it must be a windows service. We store
' the result ready for the next caller of this method.
_isService = (entryPoint.ReflectedType.BaseType.FullName = "System.ServiceProcess.ServiceBase")
End If
' Return the cached result.
Return CBool(_isService)
End Get
End Property
Private Shared _isService As Nullable(Of Boolean) = Nothing
#End Region
End Class
什么对我有用:
执行实际服务工作的类在单独的线程中运行.
该线程从OnStart()方法中启动,并从OnStop()停止.
服务和控制台模式之间的决定取决于 Environment.UserInteractive
示例代码:
class MyService : ServiceBase { private static void Main() { if (Environment.UserInteractive) { startWorkerThread(); Console.WriteLine ("====== Press ENTER to stop threads ======"); Console.ReadLine(); stopWorkerThread() ; Console.WriteLine ("====== Press ENTER to quit ======"); Console.ReadLine(); } else { Run (this) ; } } protected override void OnStart(string[] args) { startWorkerThread(); } protected override void OnStop() { stopWorkerThread() ; } }
Jonathan,不完全是你的问题的答案,但我刚刚完成了一个Windows服务,并注意到调试和测试的困难.
通过简单地将所有实际处理代码写入单独的类库程序集中解决了这个问题,然后由Windows服务可执行文件引用,以及控制台应用程序和测试工具.
除了基本的计时器逻辑之外,所有更复杂的处理都发生在通用程序集中,并且可以非常容易地按需测试/运行.
我已经修改了ProjectInstaller来附加命令行参数参数/ service,当它作为服务安装时:
static class Program { static void Main(string[] args) { if (Array.Exists(args, delegate(string arg) { return arg == "/install"; })) { System.Configuration.Install.TransactedInstaller ti = null; ti = new System.Configuration.Install.TransactedInstaller(); ti.Installers.Add(new ProjectInstaller()); ti.Context = new System.Configuration.Install.InstallContext("", null); string path = System.Reflection.Assembly.GetExecutingAssembly().Location; ti.Context.Parameters["assemblypath"] = path; ti.Install(new System.Collections.Hashtable()); return; } if (Array.Exists(args, delegate(string arg) { return arg == "/uninstall"; })) { System.Configuration.Install.TransactedInstaller ti = null; ti = new System.Configuration.Install.TransactedInstaller(); ti.Installers.Add(new ProjectInstaller()); ti.Context = new System.Configuration.Install.InstallContext("", null); string path = System.Reflection.Assembly.GetExecutingAssembly().Location; ti.Context.Parameters["assemblypath"] = path; ti.Uninstall(null); return; } if (Array.Exists(args, delegate(string arg) { return arg == "/service"; })) { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new MyService() }; ServiceBase.Run(ServicesToRun); } else { Console.ReadKey(); } } }
然后修改ProjectInstaller.cs以覆盖OnBeforeInstall()和OnBeforeUninstall()
[RunInstaller(true)] public partial class ProjectInstaller : Installer { public ProjectInstaller() { InitializeComponent(); } protected virtual string AppendPathParameter(string path, string parameter) { if (path.Length > 0 && path[0] != '"') { path = "\"" + path + "\""; } path += " " + parameter; return path; } protected override void OnBeforeInstall(System.Collections.IDictionary savedState) { Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); base.OnBeforeInstall(savedState); } protected override void OnBeforeUninstall(System.Collections.IDictionary savedState) { Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); base.OnBeforeUninstall(savedState); } }