377 lines
13 KiB
Markdown
377 lines
13 KiB
Markdown
|
|
# 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
|