54 Stimmen

Bin ich als Dienst tätig?

Ich schreibe gerade einen kleinen Bootstrap-Code für einen Dienst, der in der Konsole ausgeführt werden kann. Es läuft im Wesentlichen darauf hinaus, die OnStart()-Methode aufzurufen, anstatt die ServiceBase zu verwenden, um den Dienst zu starten und zu stoppen (weil die Anwendung nicht ausgeführt wird, wenn sie nicht als Dienst installiert ist und die Fehlersuche zu einem Albtraum wird).

Im Moment verwende ich Debugger.IsAttached, um festzustellen, ob ich ServiceBase.Run oder [service].OnStart verwenden sollte, aber ich weiß, dass ist nicht die beste Idee, weil einige Male Endbenutzer den Dienst in einer Konsole ausführen möchten (um die Ausgabe usw. in Echtzeit zu sehen).

Haben Sie eine Idee, wie ich feststellen kann, ob der Windows-Dienstcontroller "mich" gestartet hat oder ob der Benutzer "mich" in der Konsole gestartet hat? Anscheinend Umgebung.IsUserInteractive ist nicht die Antwort. Ich dachte an die Verwendung von Befehlszeilen-Args, aber das scheint "schmutzig".

Ich könnte immer sehen, über eine Try-Catch-Anweisung um ServiceBase.Run, aber das scheint schmutzig. Bearbeiten: Try-Catch funktioniert nicht.

Ich habe eine Lösung: Ich stelle sie hier für alle anderen interessierten Stacker auf:

    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: Es gab eine andere Frage auf StackOverflow, wo der Kerl Probleme mit dem Environment.CurrentDirectory hatte, das " C:\Windows\System32 "Sieht so aus, als könnte das die Antwort sein. Ich werde das heute testen.

0 Stimmen

Vielen Dank, dass Sie Ihre Lösung hinzugefügt haben, das sollte eine nützliche Referenz sein.

2 Stimmen

Nicht, dass IsUserInteractive no für Konsolenanwendungen false zurückgeben, wie es in dem Link, den Sie oben angegeben haben, angegeben wurde - zumindest nicht generell. Ich benutze es für diesen Zweck und hatte nie irgendwelche Probleme damit.

0 Stimmen

27voto

rnr_never_dies Punkte 269

Ein weiterer Workaround.. so kann als WinForm oder als Windows-Dienst laufen

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);
}

5 Stimmen

Mir gefällt diese Lösung, sie scheint das zu sein, was Environment.UserInteractive wurde konzipiert für.

1 Stimmen

Ich frage mich, was passieren würde, wenn Sie die Option "Interaktion zwischen Dienst und Desktop zulassen" für diesen Dienst aktiviert hätten. Soweit ich weiß, würde dies dem Dienst erlauben, eine grafische Benutzeroberfläche zu haben. Müsste die Eigenschaft UserInteractive dann nicht true zurückgeben? [MSDN:Die Eigenschaft UserInteractive meldet false für einen Windows-Prozess oder einen Dienst wie IIS, der ohne Benutzeroberfläche läuft].

4 Stimmen

Ich habe getestet: Wenn Sie die Option "Interaktion des Dienstes mit dem Desktop zulassen" aktivieren, ist UserInteractive wahr.

22voto

Sean Punkte 58522

Normalerweise kennzeichne ich meinen Windows-Dienst als Konsolenanwendung, die den Befehlszeilenparameter "-console" benötigt, um auf einer Konsole zu laufen, andernfalls läuft sie als Dienst. Zum Debuggen setzen Sie die Befehlszeilenparameter in den Projektoptionen einfach auf "-console" und schon kann es losgehen!

Dies erleichtert die Fehlersuche und bedeutet, dass die Anwendung standardmäßig als Dienst funktioniert, was Sie ja auch wollen.

4 Stimmen

Genau so mache ich es auch. Funktioniert sehr gut; die einzige gotcha mit Debugging dann ist die Sicherheit (die Konto) und der Arbeitsordner - die einfacher zu Code um sind.

18voto

gyrolf Punkte 3672

Was für mich funktioniert:

  • Die Klasse, die die eigentliche Servicearbeit leistet, läuft in einem separaten Thread.
  • Dieser Thread wird durch die Methode OnStart() gestartet und durch OnStop() gestoppt.
  • Die Entscheidung zwischen Service- und Konsolenmodus hängt ab von Environment.UserInteractive

Beispiel-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() ;
    }
}

0 Stimmen

Danke für den Tipp gyrolf, aber leider gilt Environment.UserInteractive nur für Windows Forms Anwendungen :(.

7 Stimmen

So wie ich die Dokumentation und den darin enthaltenen Beispielcode verstehe, gibt es keine Beschränkung auf Windows Forms-Anwendungen. Ich verwende es erfolgreich in normalen Konsolenanwendungen.

4 Stimmen

Ich kann bestätigen, dass dies richtig ist. Environment.UserInteractive ist True, wenn es auf einer Konsole läuft, und False, wenn es als Dienst läuft.

16voto

Kramii Punkte 8269

Wie Ash schreibe ich den gesamten Verarbeitungscode in einer separaten Klassenbibliotheks-Assembly, die dann von der ausführbaren Windows-Dienstdatei und einer Konsolenanwendung referenziert wird.

Es gibt jedoch Gelegenheiten, bei denen es nützlich ist, zu wissen, ob die Klassenbibliothek im Kontext des ausführbaren Dienstes oder der Konsolenanwendung ausgeführt wird. Ich mache das, indem ich die Basisklasse der Host-App reflektiere. (Entschuldigung für das VB, aber ich kann mir vorstellen, dass das Folgende ziemlich einfach in c# geändert werden kann):

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

3 Stimmen

Ich sehe nicht, wie dies funktionieren könnte, wenn die gleiche Assembly als Konsole app oder als Windows-Dienst ausgeführt werden könnte... Assembly.GetEntryAssembly() und Assembly.EntryPoint geben die gleichen Werte in beiden Fällen zurück. Ich würde vermuten, dass es nur funktioniert, wenn Sie verschiedene Assemblys in den beiden Fällen ausführen.

0 Stimmen

@DanPorts: Ich habe noch nie versucht, das Gleiche zu tun. Montage sowohl als Konsolenanwendung als auch als Windows-Dienst. Manchmal ist es jedoch sinnvoll, denselben Satz von Klassen in eine App jeder Art zu kompilieren. In diesem Fall kann die obige Klasse nützlich sein, um zu bestimmen, in welchem Kontext sie verwendet wird.

0 Stimmen

Ich bekomme "System.Object" 代わりに "System.ServiceProcess.ServiceBase" Wert als Rückgabewert von ..ReflectedType.BaseType.FullName (und ja, ich führe den Code als Dienst über das Fenster Dienste aus) ?

10voto

Rolf Kristensen Punkte 14300

Ich habe den ProjectInstaller so modifiziert, dass er den Befehlszeilenparameter /service anfügt, wenn er als Dienst installiert wird:

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();
        }
    }
}

Die Datei ProjectInstaller.cs wird dann so geändert, dass ein OnBeforeInstall() und OnBeforeUninstall() überschrieben wird

[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);
    }
}

1 Stimmen

Das obige Beispiel behandelt die Anführungszeichen nicht richtig, hier eine bessere Lösung stackoverflow.com/questions/4862580/

0 Stimmen

Verbesserte Behandlung von Anführungszeichen um den Pfad

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X