common/Hardware/Interfaces/Protocols/ProtocolCore/BaseProtocol.cs
2026-04-23 17:50:07 +02:00

156 lines
5.2 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Threading;
using Xylem.Common.CommonCore.ThreadWatcher;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore.EventArguments;
using Xylem.Common.Hardware.Interfaces.Protocols.TransmitProtocol;
namespace Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore
{
/// <inheritdoc />
/// <summary>
/// Base for all protocol types
/// </summary>
public abstract class BaseProtocol : IProtocol
{
private readonly CancellationTokenSource _decodingToken = new CancellationTokenSource();
/// <inheritdoc />
public abstract event EventHandler<BaseDataEventArgs> OnRecordIsDecoded;
/// <inheritdoc />
public virtual event EventHandler<BasePortDataEventArgs> OnRecordReadyToSend;
private readonly ConcurrentQueue<IPortDataEventArgs> _decodingFifo = new ConcurrentQueue<IPortDataEventArgs>();
//initially do not signal event
private readonly AutoResetEvent _onSyncDecodingThread = new AutoResetEvent(false);
private readonly Thread _decodingThread;
/// <summary>
/// Ident is a combined string of Slot, Port, Protocol and Type
/// </summary>
protected readonly String Ident;
private ITransmitProtocol _transmitProtocol;
/// <summary>
/// Starting decoding thread
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
protected BaseProtocol(String ident)
{
Ident = ident;
//assign thread to loop
_decodingThread = new Thread(DecodingThreadLoop) { Name = $"{Ident} Decoding thread" };
//start DecodingThread
ThreadWatcher.Instance.Start(_decodingThread);
}
/// <inheritdoc />
/// <summary>
/// Kill the decoding thread
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Oct-25" author="T.Wiedebusch">
/// - try catch block.
/// </remarks>
public virtual void Dispose()
{
try
{
//Cancel receive tokens
_decodingToken.Cancel();
//Run thread again to notice CancellationToken has changed
_onSyncDecodingThread.Set();
//this timeout counter is being used for dispose only
var timeoutCounter = 100;
while (_decodingThread.ThreadState != ThreadState.Stopped && timeoutCounter > 0)
{
Thread.Sleep(1);
timeoutCounter -= 1;
//Run thread again to notice CancellationToken has changed
_onSyncDecodingThread.Set();
}
if (_decodingThread.ThreadState != ThreadState.Stopped)
{
//if thread still running, he stuck so try to abort
// try to avoid abort (takes age to run and is not safe)
_decodingThread.Abort();
}
}
catch (Exception ex)
{
throw new ApplicationException(ex.Message);
}
}
/// <summary>
/// Fill FIFO with received data
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public void FillDecodingBuffer(IPortDataEventArgs data)
{
if (_decodingToken.IsCancellationRequested) return;
//put data to FIFO
_decodingFifo.Enqueue(data);
//put DecodingThread state from WaitSleepJoin to Running
_onSyncDecodingThread.Set();
}
/// <summary>
/// Decoding thread loop calling the individual decoding
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
private void DecodingThreadLoop()
{
try
{
while (!_decodingToken.IsCancellationRequested)
{
while (_decodingFifo.TryDequeue(out var receivedRecord))
{
DecodeRecord(receivedRecord);
}
//put DecodingThread to WaitSleepJoin until next data arrived
_onSyncDecodingThread.WaitOne();
}
}
catch (ThreadAbortException)
{ }
}
/// <summary>
/// The decoding routine
/// </summary>
/// <param name="data"></param>
protected abstract void DecodeRecord(IPortDataEventArgs data);
/// <inheritdoc />
public ITransmitProtocol GetTransmitProtocol()
{
return _transmitProtocol;
}
/// <inheritdoc />
public void SetTransmitProtocol(ITransmitProtocol transmitProtocol)
{
_transmitProtocol = transmitProtocol;
}
}
}