Files
DP44/GLM5Analysis/PatternLibrary/ServicePattern.md
2026-04-17 14:55:32 -04:00

13 KiB

Service Pattern (Background Services)

When to Use

  • Implementing hardware communication (SLICE, TDAS, Ribeye)
  • Long-running background operations
  • Real-time data acquisition
  • Download operations from hardware devices
  • Diagnostics and calibration services

Architecture Overview

┌──────────────────────────────────────────────────────────────────────┐
│                         ServiceBase                                    │
│  ┌─────────────────────────────────────────────────────────────────┐ │
│  │  Callback Pattern: ServiceCallback(CallbackData)                 │ │
│  │  - Progress: Report progress percentage                          │ │
│  │  - NewData: Real-time sample data                                │ │
│  │  - Success/Failure: Operation completion                         │ │
│  │  - Canceled: User cancellation                                   │ │
│  └─────────────────────────────────────────────────────────────────┘ │
│                                                                       │
│  State Machine: Stateless-based state transitions                     │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌──────────┐          │
│  │ Prepare │───>│Configure│───>│   Arm   │───>│ Realtime │          │
│  └─────────┘    └─────────┘    └─────────┘    └──────────┘          │
│       │              │              │              │                 │
│       v              v              v              v                 │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌──────────┐          │
│  │ Diagnose│    │ Download│    │ Disarm  │    │  Stop    │          │
│  └─────────┘    └─────────┘    └─────────┘    └──────────┘          │
└──────────────────────────────────────────────────────────────────────┘

Files to Create/Modify

Structure

IService/
├── Classes/
│   ├── ServiceBase.cs           (Base class for all services)
│   ├── GenericServices.cs       (Service orchestration)
│   ├── SLICEService/
│   │   ├── SLICE Service.cs
│   │   ├── SLICE Service.Configuration.cs
│   │   ├── SLICE Service.Realtime.cs
│   │   └── SLICE Service.Download.cs
│   └── TDAS Service/
├── Interfaces/
│   ├── IConfigurationActions.cs
│   ├── IRealTimeActions.cs
│   ├── IDownloadActions.cs
│   └── IDiagnosticsActions.cs
└── StateMachine/
    ├── States.cs
    ├── Triggers.cs
    └── States/
        ├── Configure.cs
        ├── Realtime.cs
        └── Download.cs

Code Templates

1. Service Base Class Pattern

using System;
using System.Threading;
using DTS.Common.Utilities.Logging;

namespace DTS.DASLib.Service
{
    public abstract class ServiceBase : IDisposable
    {
        public bool AggregateProgress { get; set; }

        public class CallbackData
        {
            public enum CallbackStatus
            {
                Progress,
                NewData,
                AllFinished,
                Success,
                Failure,
                Canceled
            }

            public CallbackStatus Status { get; set; }
            public IDASCommunication Target { get; set; }
            public string ErrorMessage { get; set; }
            public Exception ErrorException { get; set; }
            public int ProgressValue { get; set; }
            public SampleData[] Data { get; set; }
        }

        public delegate void Callback(CallbackData data);
        public delegate void ServiceBaseEventHandler(object sender, CallbackData data);

        public event ServiceBaseEventHandler ServiceAvailable;
        public event ServiceCallbackErrorEventHandler ServiceCallbackError;

        public abstract string ServiceName();

        public virtual void Cancel()
        {
            APILogger.LogString($"Entering {ServiceName()}.Cancel");
            DASServiceLock.Cancel(this);
        }

        protected void RunService(Callback userCallback, object userObject)
        {
            var glue = new ServiceGlueClass(userCallback, userObject);
            // Service orchestration logic
        }

        protected void FireCallback(CallbackData data)
        {
            // Fire callback to user
        }
    }
}

2. Hardware Service Implementation

using System;
using System.Threading;
using DTS.Common.DASResource;
using DTS.Common.Interface.DASFactory;

namespace DTS.DASLib.Service
{
    public class SLICEService : ServiceBase, 
        IConfigurationActions, 
        IRealTimeActions, 
        IDownloadActions
    {
        public override string ServiceName() => "SLICE Service";

        #region Configuration

        public void Configure(
            IDASCommunication[] targets,
            IServiceConfiguration config,
            Callback callback,
            object userObject)
        {
            APILogger.LogString($"{ServiceName()}.Configure starting");

            try
            {
                foreach (var target in targets)
                {
                    var slice = target as ISLICE;
                    if (slice == null) continue;

                    ApplyConfiguration(slice, config);
                }

                callback?.Invoke(new CallbackData
                {
                    Status = CallbackData.CallbackStatus.Success,
                    Target = null
                });
            }
            catch (Exception ex)
            {
                callback?.Invoke(new CallbackData
                {
                    Status = CallbackData.CallbackStatus.Failure,
                    ErrorException = ex,
                    ErrorMessage = ex.Message
                });
            }
        }

        #endregion

        #region Realtime

        public void StartRealtime(
            IDASCommunication[] targets,
            Callback callback,
            object userObject)
        {
            APILogger.LogString($"{ServiceName()}.StartRealtime");

            RunService(callback, userObject);

            foreach (var target in targets)
            {
                var slice = target as ISLICE;
                slice?.StartRealtime(OnRealtimeData, target);
            }
        }

        private void OnRealtimeData(SampleData[] data, object context)
        {
            FireCallback(new CallbackData
            {
                Status = CallbackData.CallbackStatus.NewData,
                Data = data,
                Target = context as IDASCommunication
            });
        }

        public void StopRealtime(IDASCommunication[] targets)
        {
            foreach (var target in targets)
            {
                var slice = target as ISLICE;
                slice?.StopRealtime();
            }
        }

        #endregion

        #region Download

        public void Download(
            IDASCommunication[] targets,
            IDownloadParameters parameters,
            Callback callback,
            object userObject)
        {
            APILogger.LogString($"{ServiceName()}.Download");

            foreach (var target in targets)
            {
                var slice = target as ISLICE;
                slice?.Download(
                    parameters,
                    (progress, data, error) =>
                    {
                        if (!string.IsNullOrEmpty(error))
                        {
                            callback?.Invoke(new CallbackData
                            {
                                Status = CallbackData.CallbackStatus.Failure,
                                ErrorMessage = error,
                                Target = target
                            });
                        }
                        else if (data != null)
                        {
                            callback?.Invoke(new CallbackData
                            {
                                Status = CallbackData.CallbackStatus.NewData,
                                Data = data,
                                ProgressValue = progress,
                                Target = target
                            });
                        }
                    });
            }

            callback?.Invoke(new CallbackData
            {
                Status = CallbackData.CallbackStatus.AllFinished,
                Target = null
            });
        }

        #endregion
    }
}

3. Service Interface

namespace DTS.DASLib.Service.Interfaces
{
    public interface IRealTimeActions
    {
        void StartRealtime(IDASCommunication[] targets, ServiceBase.Callback callback, object userObject);
        void StopRealtime(IDASCommunication[] targets);
    }

    public interface IDownloadActions
    {
        void Download(IDASCommunication[] targets, IDownloadParameters parameters, 
            ServiceBase.Callback callback, object userObject);
    }

    public interface IConfigurationActions
    {
        void Configure(IDASCommunication[] targets, IServiceConfiguration config,
            ServiceBase.Callback callback, object userObject);
    }
}

Examples from Codebase

Example 1: ServiceBase Callback Pattern

File: DataPRO/IService/Classes/GenericServices.cs:35-122

public abstract class ServiceBase : IDisposable
{
    public class CallbackData
    {
        public enum CallbackStatus { Progress, NewData, AllFinished, Success, Failure, Canceled }
        
        public CallbackStatus Status { get; set; }
        public IDASCommunication Target { get; set; }
        public string ErrorMessage { get; set; }
        public Exception ErrorException { get; set; }
        public int ProgressValue { get; set; }
        public SampleData[] Data { get; set; }
    }

    public delegate void Callback(CallbackData data);
    public event ServiceBaseEventHandler ServiceAvailable;

    public abstract string ServiceName();

    public virtual void Cancel()
    {
        APILogger.LogString($"Entering {ServiceName()}.Cancel");
        DASServiceLock.Cancel(this);
    }
}

Example 2: Service with Progress Reporting

File: DataPRO/IService/Classes/GenericServices.cs:198

protected void ServiceIsAvailable(object sender, CallbackData data)
{
    ServiceAvailable -= ServiceIsAvailable;
    // Handle service completion
}

protected class ServiceGlueClass
{
    public object UserObject { get; set; }
    public Callback UserCallback { get; set; }
    public ManualResetEvent AvailableEvent { get; set; }

    public ServiceGlueClass(Callback userCallback, object userObject)
    {
        UserObject = userObject;
        UserCallback = userCallback;
        AvailableEvent = new ManualResetEvent(false);
    }
}

Example 3: DASModule Configuration

File: DataPRO/IService/Classes/DASModule.cs

public class DASModule
{
    public List<AnalogInputDASChannel> AnalogInputChannels { get; set; }
    public List<DigitalOutputDASChannel> DigitalOutputChannels { get; set; }
    
    public void Configure(IServiceConfiguration config)
    {
        // Apply configuration to all channels
    }
}

Common Mistakes to Avoid

  1. Not handling cancellation - Always implement Cancel() to clean up resources
  2. Blocking the callback thread - Use async patterns, don't block in callbacks
  3. Missing error handling in callbacks - Always check for null callback before invoking
  4. Not disposing connections - Implement IDisposable properly
  5. Fire-and-forget without error logging - Always log errors even if no callback
  6. Cross-thread UI updates - Use Dispatcher.Invoke for UI updates from service callbacks
  7. Not using ServiceAvailable event - Must unsubscribe to prevent memory leaks
  8. Ignoring Target in CallbackData - Needed to identify which unit responded
  9. Not checking AggregateProgress - Affects how progress is reported for multiple units
  10. Missing ServiceName() override - Required for logging identification