13 KiB
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
- Not handling cancellation - Always implement
Cancel()to clean up resources - Blocking the callback thread - Use async patterns, don't block in callbacks
- Missing error handling in callbacks - Always check for null callback before invoking
- Not disposing connections - Implement
IDisposableproperly - Fire-and-forget without error logging - Always log errors even if no callback
- Cross-thread UI updates - Use
Dispatcher.Invokefor UI updates from service callbacks - Not using ServiceAvailable event - Must unsubscribe to prevent memory leaks
- Ignoring Target in CallbackData - Needed to identify which unit responded
- Not checking AggregateProgress - Affects how progress is reported for multiple units
- Missing ServiceName() override - Required for logging identification