// *******************************************************************************
//
// ******* *** *** *
// * * * *
// * * * *****
// * * *** * * ** * ** ***
// * * * * * * * **** * * *
// * * * * * * * * * * *
// * *** *** * ** ** ** * *
// *
// *******************************************************************************
// see https://github.com/tgoessler/TCSystemCS for details.
// Copyright (C) 2003 - 2026 Thomas Goessler. All Rights Reserved.
// *******************************************************************************
//
// TCSystem is the legal property of its developers.
// Please refer to the COPYRIGHT file distributed with this source distribution.
//
// *******************************************************************************
#region Usings
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
#endregion
namespace TCSystem.Thread;
internal sealed class WorkerThread : IWorkerThread
{
#region Public
public WorkerThread(string name, ThreadPriority priority)
{
_thread = new(RunThread)
{
Name = name,
Priority = priority
};
_running = true;
_thread.Start();
}
public void ExecuteCommand(Action action)
{
ExecuteCommand(action, null);
}
public void ExecuteCommand(Action action, string message)
{
using (_semaphoreSlim.Lock())
{
_workerActions.Enqueue((action, message));
_event.Set();
}
}
public void ClearOpenCommands()
{
using (_semaphoreSlim.Lock())
{
_workerActions.Clear();
}
}
public async Task ExecuteCommandAsync(Action action)
{
await ExecuteCommandAsync(action, null);
}
public async Task ExecuteCommandAsync(Action action, string message)
{
using (await _semaphoreSlim.LockAsync())
{
_workerActions.Enqueue((action, message));
_event.Set();
}
}
public async Task ClearOpenCommandsAsync()
{
using (await _semaphoreSlim.LockAsync())
{
_workerActions.Clear();
}
}
public void StopThread(TimeSpan? timeOut = null)
{
if (_thread is { IsAlive: true })
{
Log.Instance.Info($"Stopping Thread '{_thread.Name}'");
using (_semaphoreSlim.Lock())
{
_workerActions.Clear();
_running = false;
_cancellationTokenSource.Cancel();
_event.Set();
}
if (timeOut == null)
{
_thread.Join();
}
else
{
_thread.Join(timeOut.Value);
}
_cancellationTokenSource.Dispose();
Log.Instance.Info($"Stopping Thread '{_thread.Name} done'");
}
}
public event Action OnInitThread;
public event Action OnDeInitThread;
public event Action IdleEvent;
public int NumOpenActions
{
get
{
using (_semaphoreSlim.Lock())
{
return _workerActions.Count;
}
}
}
public bool IsBusy
{
get
{
using (_semaphoreSlim.Lock())
{
return _isBusy || _workerActions.Count > 0;
}
}
}
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
#endregion
#region Private
private void RunThread()
{
(Action Action, string Message)? action = null;
GoToIdle(false);
while (_running)
{
try
{
action = GetNextAction();
if (action == null)
{
GoToIdle(true);
_event.WaitOne();
GoToIdle(false);
}
else
{
ExecuteAction(action.Value);
}
}
catch (ThreadAbortException e)
{
if (_isBusy)
{
Log.Instance.Error($"Error executing {action?.Message}", e);
}
_running = false;
}
catch (Exception e)
{
Log.Instance.Error($"Error executing {action?.Message}", e);
}
}
if (_init)
{
OnDeInitThread?.Invoke();
_init = false;
}
}
private void ExecuteAction((Action Action, string Message) action)
{
try
{
if (!_init)
{
OnInitThread?.Invoke();
_init = true;
}
action.Action();
}
catch (OperationCanceledException)
{
Log.Instance.Info($"Thread '{_thread.Name}' canceled");
}
catch (Exception e)
{
Log.Instance.Error($"Thread '{_thread.Name}' failed.", e);
}
}
private void GoToIdle(bool idle)
{
_isBusy = !idle;
IdleEvent?.Invoke(idle);
}
private (Action Action, string Message)? GetNextAction()
{
using (_semaphoreSlim.Lock())
{
if (_workerActions.Count > 0)
{
return _workerActions.Dequeue();
}
}
return null;
}
private bool _running;
private bool _init;
private readonly System.Threading.Thread _thread;
private readonly SemaphoreSlim _semaphoreSlim = new(1);
private readonly EventWaitHandle _event = new(false, EventResetMode.AutoReset);
private readonly Queue _workerActions = new();
private readonly CancellationTokenSource _cancellationTokenSource = new();
private bool _isBusy;
#endregion
}