Files

377 lines
13 KiB
Markdown
Raw Permalink Normal View History

2026-04-17 14:55:32 -04:00
# 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
```csharp
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
```csharp
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
```csharp
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`
```csharp
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`
```csharp
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`
```csharp
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