Paskutiniu metu darbe teko nemažai rašyti įvairiausius Windows servisus (integracinius, asinchroninių užduočių ir pan.). Kaip ir daugelis jūsų, tam aš turėjau nuosavą bazinę klasę, kuri tebuvo .NET Framework klasės ServiceBase abstrakcija. Prieš kelias savaites nusprendžiau iš esmės perrašyti savo bazinę klasę, kad apart standartinio ServiceBase funkcionalumo, ji spręstų dvi pagrindines Windows servisų problemas:

  • Galimybė paleisti kodą tiek kaip konsolinę aplikaciją (derinimui), tiek kaip Windows servisą
  • Galimybė instaliuoti / išinstaliuoti servisą nesinaudojant pagalbine InstallUtil programa, o tiesiog nurodant parametrus servisas.exe /install ir servisas.exe /uninstall.

Viso sprendimo kodo nekomentuosiu (jis pakankamai aiškus), tik pažymėsiu kelias įdomias vietas:

Pavyzdinis Windows servisas galėtų atrodyti taip:

public class TestService : WindowsServiceBase
{
    public const string Name = "TestService";

    public TestService() : base(Name)
    {
    }

    private static void Main(string[] args)
    {
        Run<TestService>(args);
    }

    protected override void Execute()
    {
        Console.WriteLine("Bus spausdinama kas 60 sek.");
    }
}

Pavyzdinio Windows serviso diegėjas:

[RunInstaller(true)]
public class Installer : WindowsServiceInstaller
{
    public Installer() : base(TestService.Name)
    {
    }
}

Bazinės WindowsServiceBase klasės kodas pateiktas žemiau:

public abstract class WindowsServiceBase : ServiceBase
{
    private readonly Timer timer;

    private int isWorking;

    protected WindowsServiceBase(string name)
    {
        if (String.IsNullOrEmpty(name))
        {
            throw new ArgumentNullException("name");
        }

        timer = new Timer(PollInterval) { AutoReset = true };
        timer.Elapsed += (sender, args) => DoExecute();

        ServiceName = name;
        CanPauseAndContinue = false;
    }

    protected ILogger Logger { get; set; }

    protected int PollInterval
    {
        get
        {
            return Int32.Parse(ConfigurationManager.AppSettings["PollInterval"] ?? "60") * 1000;
        }
    }

    protected abstract void Execute();

    protected override void OnStart(string[] args)
    {
        Logger.Information("Starting service " + ServiceName);

        base.OnStart(args);
        timer.Start();
    }

    protected override void OnStop()
    {
        Logger.Information("Stopping service " + ServiceName);

        base.OnStop();
        timer.Stop();

        CleanUp();
    }

    protected static void Run<TService>(params string[] args) where TService : WindowsServiceBase, new()
    {
        var service = new TService();

        if (args != null && args.Length > 0)
        {
            for (int i = 0; i < args.Length; i++)
            {
                args[i] = args[i].Trim().ToLowerInvariant();
            }

            var exeName = Path.GetFileName(Assembly.GetCallingAssembly().Location);
            if (args.Contains("uninstall"))
            {
                using (var installer = new AssemblyInstaller(exeName, null) { UseNewContext = true })
                {
                    installer.Uninstall(null);
                    return;
                }
            }
            if (args.Contains("install"))
            {
                using (var installer = new AssemblyInstaller(exeName, null) { UseNewContext = true })
                {
                    installer.Install(null);
                    installer.Commit(null);
                    return;
                }
            }
        }

        if (Environment.UserInteractive)
        {
            service.OnStart(args);

            Console.WriteLine("Service is running... Press Ctrl+C to exit.");

            while (true)
            {
                // loop to execute service, sleep for 0.1 second to save CPU ticks
                Thread.Sleep(100)
            }
        }
        else
        {
            Run(service);
        }
    }

    protected virtual void CleanUp()
    {
        timer.Dispose();
    }

    private void DoExecute()
    {
        if (Interlocked.Exchange(ref isWorking, 1) == 0)
        {
            Logger.Information("Execution process started...");
            try
            {
                Execute();
            }
            catch (Exception e)
            {
                Logger.Error(e.Message, e);
            }
            Logger.Information("Execution process completed...");

            Interlocked.Exchange(ref isWorking, 0);
        }
    }
}

Čia naudojamas bendro pobūdžio interfeisas ILogger:

public interface ILogger
{
    void Information(string message);
    void Warning(string message);
    void Error(string message);
    void Error(string message, Exception ex);
}

Bazinė WindowsServiceInstaller klasė atrodo taip:

public class WindowsServiceInstaller : Installer
{
    private readonly ServiceProcessInstaller serviceProcessInstaller;

    private readonly ServiceInstaller serviceInstaller;

    public WindowsServiceInstaller(string name)
    {
        serviceProcessInstaller = new ServiceProcessInstaller
        {
            Account = ServiceAccount.LocalSystem,
            Password = null,
            Username = null,
        };

        serviceInstaller = new ServiceInstaller
        {
            DisplayName = name,
            ServiceName = name,
            StartType = ServiceStartMode.Automatic
        };

        Installers.AddRange(new Installer[]
            {
                serviceProcessInstaller,
                serviceInstaller
            });
    }
}

Tokia nesudėtinga bazinė klasė leido man žymiai efektyviau ir patogiau kurti bei naudoti Windows servisus, tikiuosi pravers ir jums. Kaip visada, laukiu jūsų pastabų / siūlymų / komentarų.

P.S.

WindowsServiceBase ir WindowsServiceInstaller kodą galite rasti ir mano SBToolkit projekte.

Patiko (0)

Rodyk draugams