/// <summary> /// <see cref="ServiceBase.OnStop"/> override. /// Cancels the internal <see cref="CancellationTokenSource"/> to signal stopping of the service if the service has not already been stopped. /// </summary> protected override void OnStop() { try { this.Status = AsyncServiceStatus.StopPending; // we only need to cancel the task if it hasn't already been cancelled if (!_runTask.IsCompleted) { Debug.WriteLine("Cancelling RunTask..."); _cts.Cancel(); } // await the end of all asynchronous tasks Debug.WriteLine("Thread.JoinAsync()"); _contextThread.JoinAsync().GetAwaiter().GetResult(); } catch (Exception e) { EventLog.WriteEntry(e.Message, EventLogEntryType.Error); } finally { this.Status = AsyncServiceStatus.Stopped; } }
/// <summary> /// <see cref="ServiceBase.OnStart"/> override. /// Creates an internal <see cref="Task"/> which is run on the <see cref="AsyncContextThread"/> and awaits its completion without blocking. /// Appropriately sets ServiceBase.ExitCode depending on Task completion state and logs exceptions to Windows Application Eventlog. /// </summary> /// <param name="args">The commandline arguments passed to the service.</param> protected override void OnStart(string[] args) { _cts = new CancellationTokenSource(); _pts = new PauseTokenSource(); _contextThread = new AsyncContextThread(); try { this.ExitCode = 0; this.Status = AsyncServiceStatus.StartPending; // create a new long running task _runTask = _contextThread.Factory.Run(() => RunServiceAsync(args, _cts.Token, _pts.Token)); // if the task completed normally, just stop the service and exit _runTask.ContinueWith(t => Stop(), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); // if the task was canceled in OnStop() just write a debug message _runTask.ContinueWith(t => Debug.WriteLine("ReceiveTask canceled"), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.Default); // if the task faulted, handle the exception _runTask.ContinueWith(t => HandleException(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); // await the completion of the task without blocking the OnStart() call from SCM _runTask.WaitAsync(_cts.Token).ConfigureAwait(false); } catch (Exception e) { EventLog.WriteEntry(e.Message, EventLogEntryType.Error); } finally { this.Status = AsyncServiceStatus.Running; } }
/// <summary> /// <see cref="ServiceBase.OnContinue"/> override. /// Sets the internal <see cref="PauseTokenSource"/> to false to signal continuation of the service. /// </summary> /// <exception cref="NotSupportedException"></exception> protected override void OnContinue() { #if (DEBUG) // only valid during DEBUG as SCM will not call OnContinue if PauseAndContinue is not set. if (!this.CanPauseAndContinue) { throw new NotSupportedException("Service is not configured to support PauseAndContinue."); } #endif try { this.Status = AsyncServiceStatus.ContinuePending; _pts.IsPaused = false; } catch (Exception e) { EventLog.WriteEntry(e.Message, EventLogEntryType.Error); } finally { this.Status = AsyncServiceStatus.Running; } }
/// <summary> /// Initializes a new <see cref="AsyncServiceStatusChangedEventArgs"/> instance. /// </summary> /// <param name="from">The previous <see cref="AsyncServiceStatus"/> state of the service</param> /// <param name="to">The new <see cref="AsyncServiceStatus"/> state of the service</param> public AsyncServiceStatusChangedEventArgs(AsyncServiceStatus from, AsyncServiceStatus to) { this.FromStatus = from; this.ToStatus = to; }