C# 我是作为服务运行吗
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/200163/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Am I Running as a Service
提问by Jonathan C Dickinson
I am currently writing a little bootstrap code for a service that can be run in the console. It essentially boils down to calling the OnStart() method instead of using the ServiceBase to start and stop the service (because it doesn't run the application if it isn't installed as a service and makes debugging a nightmare).
我目前正在为可以在控制台中运行的服务编写一些引导程序代码。它本质上归结为调用 OnStart() 方法,而不是使用 ServiceBase 来启动和停止服务(因为如果应用程序没有安装为服务,它就不会运行应用程序,并使调试成为一场噩梦)。
Right now I am using Debugger.IsAttached to determine if I should use ServiceBase.Run or [service].OnStart, but I know that isn't the best idea because some times end users want to run the service in a console (to see the output etc. realtime).
现在我正在使用 Debugger.IsAttached 来确定我是否应该使用 ServiceBase.Run 或 [service].OnStart,但我知道这不是最好的主意,因为有时最终用户想要在控制台中运行该服务(查看输出等。实时)。
Any ideas on how I could determine if the Windows service controller started 'me', or if the user started 'me' in the console? Apparantly Environment.IsUserInteractiveis not the answer. I thought about using commandline args, but that seems 'dirty'.
关于如何确定 Windows 服务控制器是否启动了“我”,或者用户是否在控制台中启动了“我”的任何想法?显然Environment.IsUserInteractive不是答案。我想过使用命令行参数,但这似乎很“脏”。
I could always see about a try-catch statement around ServiceBase.Run, but that seems dirty. Edit: Try catch doesn't work.
我总能看到关于 ServiceBase.Run 的 try-catch 语句,但这看起来很脏。编辑:尝试捕获不起作用。
I have a solution: putting it up here for all the other interested stackers:
我有一个解决方案:把它放在这里供所有其他感兴趣的堆垛机使用:
public void Run()
{
if (Debugger.IsAttached || Environment.GetCommandLineArgs().Contains<string>("-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();
}
}
EDIT: There was another question on StackOverflow where the guy had problems with the Environment.CurrentDirectory being "C:\Windows\System32" looks like that may be the answer. I will test today.
编辑:StackOverflow 上还有另一个问题,他的 Environment.CurrentDirectory 有问题,“C:\Windows\System32”看起来可能就是答案。我今天会测试。
采纳答案by Kramii
Like Ash, I write all actual processing code in a separate class library assembly, which was then referenced by the windows service executable, as well as a console app.
像 Ash 一样,我在一个单独的类库程序集中编写所有实际处理代码,然后由 Windows 服务可执行文件和控制台应用程序引用。
However, there are occasions when it is useful to know if the class library is running in the context of the service executable or the console app. The way I do this is to reflect on the base class of the hosting app. (Sorry for the VB, but I imagine that the following could be c#-ified fairly easily):
但是,在某些情况下,了解类库是在服务可执行文件或控制台应用程序的上下文中运行很有用。我这样做的方法是反映托管应用程序的基类。(对不起 VB,但我想以下内容可以很容易地被 c#-ified):
Public Class ExecutionContext
''' <summary>
''' Gets a value indicating whether the application is a windows service.
''' </summary>
''' <value>
''' <c>true</c> if this instance is service; otherwise, <c>false</c>.
''' </value>
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
回答by Sean
I usually flag my Windows service as a console application which takes a command line parameter of "-console" to run using a console, otherwise it runs as a service. To debug you just set the command line parameters in the project options to "-console" and you're off!
我通常将我的 Windows 服务标记为控制台应用程序,它使用“-console”的命令行参数来使用控制台运行,否则它作为服务运行。要调试,您只需将项目选项中的命令行参数设置为“-console”即可!
This makes debugging nice and easy and means that the app functions as a service by default, which is what you'll want.
这使调试变得简单而轻松,这意味着应用程序默认作为服务运行,这正是您想要的。
回答by Ash
Jonathan, not exactly an answer to your question, but I've just finished writing a windows service and also noted the difficulty with debugging and testing.
乔纳森,不完全是您问题的答案,但我刚刚完成了 Windows 服务的编写,并且还注意到调试和测试的困难。
Solved it by simply writing all actual processing code in a separate class library assembly, which was then referenced by the windows service executable, as well as a console app and a test harness.
通过简单地在单独的类库程序集中编写所有实际处理代码来解决它,然后由 Windows 服务可执行文件以及控制台应用程序和测试工具引用。
Apart from basic timer logic, all more complex processing happened in the common assembly and could be tested/run on demand incredibly easily.
除了基本的计时器逻辑之外,所有更复杂的处理都发生在公共程序集中,并且可以非常轻松地根据需要进行测试/运行。
回答by mdb
The only way I've found to achieve this, is to check if a console is attached to the process in the first place, by accessing any Console object property (e.g. Title) inside a try/catch block.
我发现实现此目的的唯一方法是首先检查控制台是否附加到进程,方法是访问 try/catch 块内的任何控制台对象属性(例如标题)。
If the service is started by the SCM, there is no console, and accessing the property will throw a System.IO.IOError.
如果服务是由 SCM 启动的,则没有控制台,访问该属性会抛出 System.IO.IOError。
However, since this feels a bit too much like relying on an implementation-specific detail (what if the SCM on some platforms or someday decides to provide a console to the processes it starts?), I always use a command line switch (-console) in production apps...
但是,由于这感觉有点过于依赖特定于实现的细节(如果某些平台上的 SCM 或某天决定为它启动的进程提供控制台怎么办?),我总是使用命令行开关(-console ) 在生产应用程序中...
回答by Anderson Imes
This is a bit of a self-plug, but I've got a little app that will load up your service types in your app via reflection and execute them that way. I include the source code, so you could change it slightly to display standard output.
这有点自插,但我有一个小应用程序,它将通过反射在您的应用程序中加载您的服务类型并以这种方式执行它们。我包含了源代码,因此您可以稍微更改它以显示标准输出。
No code changes needed to use this solution. I have a Debugger.IsAttached type of solution as well that is generic enough to be used with any service. Link is in this article: .NET Windows Service Runner
使用此解决方案无需更改代码。我还有一个 Debugger.IsAttached 类型的解决方案,它足够通用,可以与任何服务一起使用。链接在这篇文章中: .NET Windows Service Runner
回答by gyrolf
What works for me:
什么对我有用:
- The class doing the actual service work is running in a separate thread.
- This thread is started from within the OnStart() method, and stopped from OnStop().
- The decision between service and console mode depends on
Environment.UserInteractive
- 执行实际服务工作的类在单独的线程中运行。
- 该线程从 OnStart() 方法内启动,并从 OnStop() 停止。
- 服务模式和控制台模式之间的决定取决于
Environment.UserInteractive
Sample code:
示例代码:
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() ;
}
}
回答by Kramii
Maybe checking if the process parent is C:\Windows\system32\services.exe.
也许检查进程父进程是否为 C:\Windows\system32\services.exe。
回答by Rolf Kristensen
I have modified the ProjectInstaller to append the command-line argument parameter /service, when it is being installed as service:
我修改了 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();
}
}
}
The ProjectInstaller.cs is then modified to override a OnBeforeInstall() and OnBeforeUninstall()
然后修改 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);
}
}
回答by rnr_never_dies
Another workaround.. so can run as WinForm or as windows service
另一种解决方法..所以可以作为 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);
}
回答by shockwave121
This thread is really old, but I thought I would throw my solution out there. Quite simply, to handle this type of situation, I built a "service harness" that is used in both the console and Windows service cases. As above, most of the logic is contained in a separate library, but this is more for testing and "linkability".
这个线程真的很旧,但我想我会把我的解决方案扔在那里。很简单,为了处理这种情况,我构建了一个“服务工具”,用于控制台和 Windows 服务案例。如上所述,大部分逻辑都包含在一个单独的库中,但这更多是为了测试和“可链接性”。
The attached code by no means represents the "best possible" way to solve this, just my own approach. Here, the service harness is called by the console app when in "console mode" and by the same application's "start service" logic when it is running as a service. By doing it this way, you can now call
附加的代码绝不代表解决这个问题的“最佳”方法,只是我自己的方法。在这里,服务工具在处于“控制台模式”时由控制台应用程序调用,在作为服务运行时由同一应用程序的“启动服务”逻辑调用。通过这样做,您现在可以调用
ServiceHost.Instance.RunningAsAService
(Boolean)
ServiceHost.Instance.RunningAsAService
(布尔值)
from anywhere in your code to check if the application is running as a service or simply as a console.
从代码中的任何位置检查应用程序是作为服务运行还是仅作为控制台运行。
Here is the code:
这是代码:
public class ServiceHost
{
private static Logger log = LogManager.GetLogger(typeof(ServiceHost).Name);
private static ServiceHost mInstance = null;
private static object mSyncRoot = new object();
#region Singleton and Static Properties
public static ServiceHost Instance
{
get
{
if (mInstance == null)
{
lock (mSyncRoot)
{
if (mInstance == null)
{
mInstance = new ServiceHost();
}
}
}
return (mInstance);
}
}
public static Logger Log
{
get { return log; }
}
public static void Close()
{
lock (mSyncRoot)
{
if (mInstance.mEngine != null)
mInstance.mEngine.Dispose();
}
}
#endregion
private ReconciliationEngine mEngine;
private ServiceBase windowsServiceHost;
private UnhandledExceptionEventHandler threadExceptionHanlder = new UnhandledExceptionEventHandler(ThreadExceptionHandler);
public bool HostHealthy { get; private set; }
public bool RunningAsService {get; private set;}
private ServiceHost()
{
HostHealthy = false;
RunningAsService = false;
AppDomain.CurrentDomain.UnhandledException += threadExceptionHandler;
try
{
mEngine = new ReconciliationEngine();
HostHealthy = true;
}
catch (Exception ex)
{
log.FatalException("Could not initialize components.", ex);
}
}
public void StartService()
{
if (!HostHealthy)
throw new ApplicationException("Did not initialize components.");
try
{
mEngine.Start();
}
catch (Exception ex)
{
log.FatalException("Could not start service components.", ex);
HostHealthy = false;
}
}
public void StartService(ServiceBase serviceHost)
{
if (!HostHealthy)
throw new ApplicationException("Did not initialize components.");
if (serviceHost == null)
throw new ArgumentNullException("serviceHost");
windowsServiceHost = serviceHost;
RunningAsService = true;
try
{
mEngine.Start();
}
catch (Exception ex)
{
log.FatalException("Could not start service components.", ex);
HostHealthy = false;
}
}
public void RestartService()
{
if (!HostHealthy)
throw new ApplicationException("Did not initialize components.");
try
{
log.Info("Stopping service components...");
mEngine.Stop();
mEngine.Dispose();
log.Info("Starting service components...");
mEngine = new ReconciliationEngine();
mEngine.Start();
}
catch (Exception ex)
{
log.FatalException("Could not restart components.", ex);
HostHealthy = false;
}
}
public void StopService()
{
try
{
if (mEngine != null)
mEngine.Stop();
}
catch (Exception ex)
{
log.FatalException("Error stopping components.", ex);
HostHealthy = false;
}
finally
{
if (windowsServiceHost != null)
windowsServiceHost.Stop();
if (RunningAsService)
{
AppDomain.CurrentDomain.UnhandledException -= threadExceptionHanlder;
}
}
}
private void HandleExceptionBasedOnExecution(object ex)
{
if (RunningAsService)
{
windowsServiceHost.Stop();
}
else
{
throw (Exception)ex;
}
}
protected static void ThreadExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
log.FatalException("Unexpected error occurred. System is shutting down.", (Exception)e.ExceptionObject);
ServiceHost.Instance.HandleExceptionBasedOnExecution((Exception)e.ExceptionObject);
}
}
All you need to do here is replace that ominous looking ReconcilationEngine
reference with whatever method is boostrapping your logic. Then in your application, use the ServiceHost.Instance.Start()
and ServiceHost.Instance.Stop()
methods whether you are running in console mode or as a service.
您在这里需要做的就是ReconcilationEngine
用任何增强逻辑的方法替换那个看起来不祥的参考。然后在您的应用程序中,无论您是在控制台模式下运行还是作为服务运行,都使用ServiceHost.Instance.Start()
和ServiceHost.Instance.Stop()
方法。