Update con prima separazione metodi pooled/single (da validare)

This commit is contained in:
Samuele Locatelli
2026-02-17 19:15:53 +01:00
parent e05157d618
commit 591fae86ee
11 changed files with 653 additions and 273 deletions
+1 -1
View File
@@ -80,7 +80,7 @@ namespace IOB_UT_NEXT.Config.Base
/// <summary>
/// Soglia massima errori prima della disconnessione automatica in check
/// </summary>
public int MaxErroriCheck { get; set; } = 100;
public int MaxErroriCheck { get; set; } = 50;
/// <summary>
/// Max tentativi ping permessi (default: 5)
+1 -1
View File
@@ -274,7 +274,7 @@ namespace IOB_UT_NEXT.Config
PzTotMode = fIni.ReadString("OPTPAR", "PZGTOT_MODE", ""),
ReadErrorMax = fIni.ReadInteger("OPTPAR", "READ_ERROR_MAX", 20),
ReadErrorSleepTime = fIni.ReadInteger("OPTPAR", "READ_ERROR_SLEEP_TIME", 20000),
StartupVetoQueueIN = fIni.ReadInteger("OPTPAR", "VETO_QUEUE_IN", 10),
StartupVetoQueueIN = fIni.ReadInteger("OPTPAR", "VETO_QUEUE_IN", 6),
Vendor = fIni.ReadString("MACHINE", "VENDOR", "STEAMWARE"),
Connect = new ConnectionDto()
{
+21 -24
View File
@@ -8,43 +8,40 @@ public class SingleThreadTaskScheduler : TaskScheduler, IDisposable
{
private readonly BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
private readonly Thread _thread;
public SynchronizationContext SyncContext { get; private set; }
public SingleThreadTaskScheduler()
public SingleThreadTaskScheduler(string threadName)
{
var tcs = new TaskCompletionSource<bool>();
_thread = new Thread(() =>
{
// Creiamo il contesto di sincronizzazione personalizzato
SyncContext = new SingleThreadSynchronizationContext(this);
SynchronizationContext.SetSynchronizationContext(SyncContext);
tcs.SetResult(true);
foreach (var task in _tasks.GetConsumingEnumerable())
{
// Esegue il task sul thread dedicato
base.TryExecuteTask(task);
}
});
_thread.IsBackground = true;
_thread.Name = "SingleWorkerThread";
})
{ IsBackground = true, Name = threadName };
_thread.Start();
tcs.Task.Wait(); // Aspetta che il thread sia pronto col suo contesto
}
protected override void QueueTask(Task task)
private class SingleThreadSynchronizationContext : SynchronizationContext
{
_tasks.Add(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Consente l'esecuzione inline solo se siamo già sul thread dedicato
return Thread.CurrentThread == _thread && base.TryExecuteTask(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return _tasks.ToArray();
private readonly SingleThreadTaskScheduler _sch;
public SingleThreadSynchronizationContext(SingleThreadTaskScheduler sch) => _sch = sch;
public override void Post(SendOrPostCallback d, object state)
=> Task.Factory.StartNew(() => d(state), CancellationToken.None, TaskCreationOptions.None, _sch);
}
protected override void QueueTask(Task task) => _tasks.Add(task);
protected override bool TryExecuteTaskInline(Task task, bool prev)
=> Thread.CurrentThread == _thread && base.TryExecuteTask(task);
protected override IEnumerable<Task> GetScheduledTasks() => _tasks;
public override int MaximumConcurrencyLevel => 1;
public void Dispose()
{
_tasks.CompleteAdding();
}
public void Dispose() => _tasks.CompleteAdding();
}
-44
View File
@@ -351,36 +351,6 @@ namespace IOB_UT_NEXT
}
}
#if false
/// <summary>
/// Versione async della chiamata ad URL
/// </summary>
/// <param name="URL"></param>
/// <returns></returns>
public static string callUrlAsync(string URL)
{
// Chiamo in modalità task...
var resp = Task.Run(() => callUrl(URL));
resp.Wait();
return resp.Result;
}
/// <summary>
/// Versione async della chiamata ad URL
/// </summary>
/// <param name="URL"></param>
/// <param name="payload"></param>
/// <returns></returns>
public static string callUrlAsync(string URL, string payload)
{
// Chiamo in modalità task...
var resp = Task.Run(() => callUrl(URL, payload));
resp.Wait();
return resp.Result;
}
#endif
/// <summary>
/// Effettua chiamata URL IMMEDIATAMENTE e restituisce risultato
/// </summary>
@@ -553,20 +523,6 @@ namespace IOB_UT_NEXT
public static string CRS(string key)
{
return ConfigurationManager.AppSettings[key] ?? string.Empty;
#if false
string answ = "";
if (ConfigurationManager.AppSettings.Count > 0)
{
try
{
answ = ConfigurationManager.AppSettings[key].ToString();
}
catch
{ }
}
return answ;
#endif
}
/// <summary>
+6
View File
@@ -86,6 +86,12 @@ DISABLE_SEND_WDST=TRUE
; bit da scrivere come trace ad ogni check
;MEM_2_TRACE=|BIT3|BIT4|BIT5|
PARAM_CONF=3028.json
; test timing & errori
timerIntMs=30
ExeSingleThread=true
MAX_ERR_CHECK=10
;WAIT_REC_MSEC=15000
[BRANCH]
NAME=master
+4
View File
@@ -88,6 +88,10 @@ ENABLE_SEND_PZC_BLOCK=TRUE
MIN_SEND_PZC_BLOCK=0
MAX_SEND_PZC_BLOCK=100
DISABLE_SEND_WDST=TRUE
; test timing & errori
timerIntMs=40
;ExeSingleThread=true
MAX_ERR_CHECK=10
; bit da scrivere come trace ad ogni check
;MEM_2_TRACE=|BIT3|BIT4|BIT5|
PARAM_CONF=3029.json
+2 -1
View File
@@ -26,11 +26,12 @@ CLI_INST=SteamWareSim
;STARTLIST=3004
;STARTLIST=3002
;STARTLIST=GT575
STARTLIST=3029
;STARTLIST=3004,3005
;STARTLIST=SIMUL_01
;STARTLIST=FOV106
;STARTLIST=FOV107
;STARTLIST=3028
STARTLIST=3029
MAXCNC=10
+14 -1
View File
@@ -59,6 +59,11 @@ namespace IOB_WIN_FANUC.Iob
MemBlockX = new byte[xSize];
MemBlockY = new byte[ySize];
// forzo letture single threaded x FANUC!!!
IOBConfFull.General.ExeSingleThread = true;
// x FANUC riduco num errori std x forzare riavvio rapido
maxErroriCheck = maxErroriCheck > 15 ? 15 : maxErroriCheck;
// loggo aree di memoria avviate...
lgInfo($"Init area di memoria | MemBlockG: {gSize}b | MemBlockR: {rSize}b | MemBlockX: {xSize}b | MemBlockY: {ySize}b ");
@@ -1520,6 +1525,7 @@ namespace IOB_WIN_FANUC.Iob
/// </summary>
public override void tryConnect()
{
lgInfo("++++++++++++ CONNECT ++++++++++++");
if (!connectionOk)
{
// controllo che il ping sia stato tentato almeno pingTestSec fa...
@@ -1619,6 +1625,7 @@ namespace IOB_WIN_FANUC.Iob
/// </summary>
public override void tryDisconnect()
{
lgInfo("----------- DISCONNECT -----------");
if (connectionOk)
{
string szStatusConnection = "";
@@ -3008,13 +3015,19 @@ namespace IOB_WIN_FANUC.Iob
private void tryIncreaseErrorComm(string topic)
{
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | numErroriCheck: {numErroriCheck}");
numErroriCheck++;
parentForm.ChangeErrorDelay(topic, numErroriCheck, 5);
}
private void tryReduceErrorCount(string topic)
{
numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : numErroriCheck;
numErroriCheck -= 1;
if (numErroriCheck>=0)
{
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | numErroriCheck: {numErroriCheck}");
}
numErroriCheck = numErroriCheck < 0 ? 0 : numErroriCheck;
parentForm.ChangeErrorDelay(topic, numErroriCheck, -5);
}
+485 -86
View File
@@ -275,7 +275,7 @@ namespace IOB_WIN_FORM
{
// update veto!
dMonDisplVetoVeto[1] = adesso.AddMilliseconds(delayShowLogMs);
if (!ShouldUpdateUI()) return;
// se scaduto veto --> display!
lblOutMessage.Text = String.Join(Environment.NewLine, dMonValues[1]);
@@ -748,8 +748,14 @@ namespace IOB_WIN_FORM
restart.Enabled = true;
// 2026.01.02 fix timers al restart
gather.Enabled = true;
// 2026.02.17 controlo stato single thread exe...
doExeSingleThread = iobObj.IOBConfFull.General.ExeSingleThread;
#if true
// gestione worker
StartWorker();
#endif
displayTaskAndLog("Start Timers", true);
// inizializzo le scadenze dei timers...
TimersVeto = new Dictionary<gatherCycle, DateTime>();
@@ -788,6 +794,31 @@ namespace IOB_WIN_FORM
}
}
/// <summary>
/// Registra variazione error delay in caso di errori o successi
/// </summary>
/// <param name="topic"></param>
/// <param name="currCount"></param>
/// <param name="delta"></param>
public void ChangeErrorDelay(string topic, int currCount, int delta)
{
_errorDelay += delta;
// se fosse negativo --> riporto a zero!
_errorDelay = _errorDelay < 0 ? 0 : _errorDelay;
// registro LOG variazione
if (delta > 0)
{
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | +++ delay {_errorDelay} | {topic} | err: {currCount} | {DateTime.Now:HH:mm:ss.fff}");
}
else
{
if (_errorDelay > 0)
{
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | --- delay {_errorDelay} | {topic} | err: {currCount} | {DateTime.Now:HH:mm:ss.fff}");
}
}
}
/// <summary>
/// mostra un testo sulla status bar + LOG
/// </summary>
@@ -1084,6 +1115,28 @@ namespace IOB_WIN_FORM
base.Dispose(disposing);
}
/// <summary>
/// Esegue logica di init async x IOB
/// </summary>
/// <returns></returns>
protected async Task<bool> iobInitAsync()
{
try
{
this.Cursor = Cursors.WaitCursor;
await iobObj.InitializeAsync();
this.Cursor = Cursors.Default;
}
catch (Exception ex)
{
lgError($"Inizializzazione fallita per {tipoScelto}: {ex.Message}");
iobObj = null; // Evitiamo di usare un oggetto malfunzionante
return false;
}
return true;
}
/// <summary>
/// GEstione evento refresh
/// </summary>
@@ -1184,28 +1237,6 @@ namespace IOB_WIN_FORM
}
}
/// <summary>
/// Esegue logica di init async x IOB
/// </summary>
/// <returns></returns>
protected async Task<bool> iobInitAsync()
{
try
{
this.Cursor = Cursors.WaitCursor;
await iobObj.InitializeAsync();
this.Cursor = Cursors.Default;
}
catch (Exception ex)
{
lgError($"Inizializzazione fallita per {tipoScelto}: {ex.Message}");
iobObj = null; // Evitiamo di usare un oggetto malfunzionante
return false;
}
return true;
}
/// <summary>
/// Init form (grafica) con helper x testi e varie
/// </summary>
@@ -1248,25 +1279,6 @@ namespace IOB_WIN_FORM
nLine2show = utils.CRI("numRowConsole");
}
/// <summary>
/// Indica se debba aggiornare o meno la UI perché minimizzata o meno
/// </summary>
/// <returns></returns>
private bool ShouldUpdateUI()
{
// 1. Controlla se la form stessa è minimizzata
if (this.WindowState == FormWindowState.Minimized) return false;
// 2. Se è una MDI Child, controlla se il Padre è minimizzato
if (this.MdiParent != null && this.MdiParent.WindowState == FormWindowState.Minimized)
return false;
// 3. (Opzionale) Controlla se la form è visibile
if (!this.Visible) return false;
return true;
}
/// <summary>
/// MOstra info su coda complessiva
/// </summary>
@@ -1296,15 +1308,44 @@ namespace IOB_WIN_FORM
#region Private Fields
/// <summary>
/// Task factory single threaded
/// </summary>
private static readonly TaskFactory _singleFactory = new TaskFactory(_singleScheduler);
/// <summary>
/// Scheduler single threaded
/// </summary>
private static readonly SingleThreadTaskScheduler _singleScheduler = new SingleThreadTaskScheduler("IOB-SINGLE");
private readonly object _lock = new object();
/// <summary>
/// Usato come semaforo x evitare doppio uso risorse threaded
/// </summary>
private readonly SemaphoreSlim _plcLock = new SemaphoreSlim(1, 1);
private CancellationTokenSource _cts;
/// <summary>
/// Ritardo per errori (extra nei cicli)
/// </summary>
private int _errorDelay = 0;
// Per thread-safety
private bool _isSuspended = false;
private FormWindowState _lastState = FormWindowState.Minimized;
private Task _workerTask;
/// <summary>
/// Booleana per determinare se eseguire le chiamate DoExecTasksAsync su single thread (es FANUC) o dinamico (default)
/// </summary>
private bool doExeSingleThread = false;
private bool stopForced = false;
/// <summary>
/// Contatore chiamate timers x debug timing
/// </summary>
@@ -1373,7 +1414,7 @@ namespace IOB_WIN_FORM
if (IOBConfFull == null || !utils.CRB("autoLoadConf"))
{
IOBConfFull = new IobConfTree();
loadIobType();
await loadIobType();
displayTaskAndLog("Waiting for config file selection");
}
@@ -1424,11 +1465,13 @@ namespace IOB_WIN_FORM
lgError($"AdapterForm: EXCEPTION in fase di chiamata URL di reboot:{iobObj.urlReboot}{Environment.NewLine}{exc}");
}
#if false
// avvio timer secondario x esecuzione (periodo di base: VHF!!!)
StartWorker();
#endif
displayTaskAndLog($"Main workerTimer set: {IOBConfFull.General.Timers.MsVHF}ms", true);
}
displayTaskAndLog("Main Form OK", true);
displayTaskAndLog("Adapter Form OK", true);
}
private void btnForceAutoOdl_Click(object sender, EventArgs e)
@@ -1512,8 +1555,10 @@ namespace IOB_WIN_FORM
/// <summary>
/// Verifica scadenza task
/// </summary>
/// <param name="ciclo"></param>
private async Task checkScad()
/// <param name="enabPool">Indica se siano abilitate operazioni Pool (Thread Safe)</param>
/// <param name="enabSTID">Indica se siano abilitate le operazioni Single Thread</param>
/// <returns></returns>
private async Task checkScad(bool enabPool, bool enabSTID)
{
DateTime adesso = DateTime.Now;
bool sendDone = false;
@@ -1532,10 +1577,13 @@ namespace IOB_WIN_FORM
{
TimersCycleCount[item]++;
Stopwatch sw = Stopwatch.StartNew();
await iobObj.getAndSendAsync(item);
// chiamata di processing x frequenza
await iobObj.getAndSendAsync(item, enabPool, enabSTID);
sw.Stop();
sendDone = true;
// metto una piccola attesa se ho altre scadenze
TimersCycleElaps[item] += sw.Elapsed.TotalMilliseconds;
// metto nuova scadenza perturbata 10%
TimersVeto[item] = adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0.1));
@@ -1592,42 +1640,68 @@ namespace IOB_WIN_FORM
/// <summary>
/// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound
/// </summary>
private async Task DoExecTasks()
/// <param name="reqSingle">Richiesta esecuzone su single thread (es FANUC)</param>
/// <returns></returns>
private async Task DoExecTasksAsync(bool reqSingle)
{
bool doLog = false;
// procedo!
if (iobObj.periodicLog)
{
doLog = true;
}
// Attende il proprio turno. Se un task è già in corso, questo aspetta.
await _plcLock.WaitAsync();
bool doLog = iobObj.periodicLog;
try
{
// check esecuzione SendTask (MsVHF) COMUNQUE...
await iobObj.getAndSendAsync(gatherCycle.VHF);
// imposto variabili x esecuzione separata task Single Thread / Pooled Thread
bool reqSTE = iobObj.IOBConfFull.General.ExeSingleThread;
bool enabSTID = reqSTE ? reqSingle : true;
bool enabPool = !reqSTE;
if (enabPool)
{
// check esecuzione SendTask (MsVHF) COMUNQUE...
await iobObj.getAndSendAsync(gatherCycle.VHF, enabPool, enabSTID);
}
// eseguo cicli attivi SOLO se adapter è in EFFETTIVO running...
if (iobObj.adpRunning)
{
if (iobObj.connectionOk)
{
// se richiesto faccio memory DUMP INIZIALE!
if (iobObj.doStartMemDump)
DateTime adesso = DateTime.Now;
// verifico non ci sia veto comunicazioni lettura...
if (iobObj.queueInEnabCurr)
{
lgInfo("Inizio dump memoria");
iobObj.saveMemDump(dumpType.STARTUP);
// fatto! non ripeto...
iobObj.doStartMemDump = false;
lgInfo("Finito dump memoria");
if (enabSTID)
{
// se richiesto faccio memory DUMP INIZIALE!
if (iobObj.doStartMemDump)
{
lgInfo("Inizio dump memoria");
iobObj.saveMemDump(dumpType.STARTUP);
// fatto! non ripeto...
iobObj.doStartMemDump = false;
lgInfo("Finito dump memoria");
}
}
if (enabPool)
{
// controllo se sia abilitato sampleDump della meoria (periodico)
if (iobObj.doSampleMemory)
{
checkSampleMem();
}
}
// MAIN: controllo TUTTE le scadenze...
await checkScad(enabPool, enabSTID);
// wait opzionale in coda
if (utils.CRI("waitEndCycle") > 0)
{
await Task.Delay(utils.CRI("waitEndCycle"));
}
}
// controllo se sia abilitato sampleDump della meoria (periodico)
if (iobObj.doSampleMemory)
else
{
checkSampleMem();
}
// controllo TUTTE le scadenze...
checkScad();
if (utils.CRI("waitEndCycle") > 0)
{
await Task.Delay(utils.CRI("waitEndCycle"));
lgTrace($"VETO queueInEnabCurr | veto attivo | {adesso:yyyy.MM.dd HH:mm:ss}");
iobObj.checkVetoQueueIn();
}
}
else
@@ -1663,7 +1737,11 @@ namespace IOB_WIN_FORM
}
catch (Exception exc)
{
lgError(string.Format("Eccezione in fase di gatherTick: {0}{1}", Environment.NewLine, exc));
lgError($"Eccezione in fase di DoExecTasksAsync: {Environment.NewLine}{exc}");
}
finally
{
_plcLock.Release();
}
}
@@ -1706,8 +1784,6 @@ namespace IOB_WIN_FORM
lgError($"Errore in fermaTutto |stopTimer: {stopTimer} | tryRestart: {tryRestart} | forceDequeue: {forceDequeue} | updateForm: {updateForm}{Environment.NewLine}{ex.Message}");
}
newDisplayData currDispData = new newDisplayData();
currDispData.semIn = Semaforo.SS;
currDispData.semOut = Semaforo.SS;
@@ -1812,7 +1888,7 @@ namespace IOB_WIN_FORM
IOBConfFull.SaveYaml(fullPath.Replace("iob", "yaml"));
// carico IOB
loadIobType();
_ = loadIobType();
// invio file di conf!
IOBConfFull.SendConfYaml(iobObj.urlSaveConfYaml);
@@ -1859,7 +1935,7 @@ namespace IOB_WIN_FORM
}
var appVers = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
loadIobType();
_ = loadIobType();
// avvio macchina con adapter specificato...
if (utils.CRB("autoStartOnLoad"))
{
@@ -1922,7 +1998,6 @@ namespace IOB_WIN_FORM
_lastState = this.WindowState;
}
private FormWindowState _lastState = FormWindowState.Minimized;
/// <summary>
/// Mostrata form
/// </summary>
@@ -2117,6 +2192,25 @@ namespace IOB_WIN_FORM
}
}
/// <summary>
/// Indica se debba aggiornare o meno la UI perché minimizzata o meno
/// </summary>
/// <returns></returns>
private bool ShouldUpdateUI()
{
// 1. Controlla se la form stessa è minimizzata
if (this.WindowState == FormWindowState.Minimized) return false;
// 2. Se è una MDI Child, controlla se il Padre è minimizzato
if (this.MdiParent != null && this.MdiParent.WindowState == FormWindowState.Minimized)
return false;
// 3. (Opzionale) Controlla se la form è visibile
if (!this.Visible) return false;
return true;
}
private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e)
{
}
@@ -2139,6 +2233,24 @@ namespace IOB_WIN_FORM
/// </summary>
private void StartWorker()
{
// Versione 1
#if false
_isSuspended = false;
_cts = new CancellationTokenSource();
// Usiamo TaskCreationOptions.LongRunning
// Questo suggerisce a .NET di creare un thread dedicato invece di usare il pool
_workerTask = Task.Factory.StartNew(
() => WorkerLoopAsync(_cts.Token),
_cts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default).Unwrap(); // Unwrap è necessario perché WorkerLoopAsync è Task<Task>
#endif
// versone 2
#if false
// rimozione isSuspended
_isSuspended = false;
// per prima cosa disattivo il disabled...
@@ -2153,10 +2265,170 @@ namespace IOB_WIN_FORM
_cts = new CancellationTokenSource();
// Assegniamo il Task alla variabile per poterne monitorare lo stato
if (doExeSingleThread)
{
// Facciamo partire tutto il loop sul thread dedicato fin dall'inizio
_workerTask = _singleFactory.StartNew(
() => WorkerLoopAsync(_cts.Token),
_cts.Token
).Unwrap();
}
else
{
_workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token));
}
}
#endif
// versione 3
#if false
if (doExeSingleThread)
{
_isSuspended = false;
_cts = new CancellationTokenSource();
// Creiamo un thread VERO, uno solo, che non morirà mai
Thread dedicatedThread = new Thread(() =>
{
lgInfo($"[THREAD FISSO: {Thread.CurrentThread.ManagedThreadId}] Avviato");
while (!_cts.Token.IsCancellationRequested)
{
try
{
if (!_isSuspended)
{
// Eseguiamo il task asincrono in modo "bloccante" per questo thread
// Ma essendo un thread separato, la UI non se ne accorge!
DoExecTasksAsync().GetAwaiter().GetResult();
}
// Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF
int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF;
if (_errorDelay > 0) currentDelay += _errorDelay;
// Dorme senza rilasciare il thread al pool
Thread.Sleep(currentDelay);
// Logga l'ID: vedrai che ora torna sempre lo stesso!
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Fine ciclo delay");
}
catch (Exception ex)
{
lgError("Errore nel loop: " + ex.Message);
}
}
});
dedicatedThread.IsBackground = true;
dedicatedThread.Name = "SingleDedicatedThread";
dedicatedThread.Start();
}
else
{
// rimozione isSuspended
_isSuspended = false;
// per prima cosa disattivo il disabled...
lock (_lock)
{
// 1. Controllo se è già in esecuzione
if (_workerTask != null && !_workerTask.IsCompleted)
{
lgTrace("Worker già in esecuzione. Richiesta ignorata.");
return;
}
_cts = new CancellationTokenSource();
_workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token));
}
}
#endif
// versione 4
if (doExeSingleThread)
{
lock (_threadLock)
{
// 1. Ferma eventuali worker precedenti
if (_cts != null)
{
_cts.Cancel();
}
// 2. Attendi che il thread precedente sia effettivamente uscito
if (_dedicatedThread != null && _dedicatedThread.IsAlive)
{
// Timeout breve per non freezare la UI, ma necessario per pulizia
_dedicatedThread.Join(500);
}
_isSuspended = false;
_cts = new CancellationTokenSource();
// 3. Crea un thread NUOVO e DEDICATO
_dedicatedThread = new Thread(() => WorkerLoopSingolo(_cts.Token))
{
IsBackground = true,
Name = "Single_Dedicated_Thread",
Priority = ThreadPriority.AboveNormal // Opzionale: dà precedenza ai dati PLC
};
_dedicatedThread.Start();
lgInfo("--- WORKER FISICO AVVIATO ---");
}
}
#if false
else
{
// rimozione isSuspended
_isSuspended = false;
// per prima cosa disattivo il disabled...
lock (_lock)
{
// 1. Controllo se è già in esecuzione
if (_workerTask != null && !_workerTask.IsCompleted)
{
lgTrace("Worker già in esecuzione. Richiesta ignorata.");
return;
}
_cts = new CancellationTokenSource();
_workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token));
}
}
#endif
// avvio altro thread comunque...
// rimozione isSuspended
_isSuspended = false;
// per prima cosa disattivo il disabled...
lock (_lock)
{
// 1. Controllo se è già in esecuzione
if (_workerTask != null && !_workerTask.IsCompleted)
{
lgTrace("Worker già in esecuzione. Richiesta ignorata.");
return;
}
_cts = new CancellationTokenSource();
_workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token));
}
}
private Thread _dedicatedThread;
private readonly object _threadLock = new object();
private bool _isProcessing = false; // Flag di sicurezza aggiuntivo
#if false
private BlockingCollection<Action> _workQueue = new BlockingCollection<Action>();
#endif
/// <summary>
/// fermata dell'adapter
/// </summary>
@@ -2170,8 +2442,6 @@ namespace IOB_WIN_FORM
lgInfo("UNLOAD Adapter");
}
private bool stopForced = false;
// Metodo per fermare tutto (es. nel Form_Closing o Dispose)
private async Task StopWorker()
{
@@ -2224,8 +2494,58 @@ namespace IOB_WIN_FORM
checkAssignSize();
}
private async Task WorkerLoopAsync(CancellationToken ct)
#if false
/// <summary>
/// Loop di gestione worker
/// </summary>
/// <param name="ct"></param>
private void WorkerLoopDestined(CancellationToken ct)
{
try
{
lgInfo($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Loop dedicato AVVIATO");
while (!ct.IsCancellationRequested)
{
if (!_isSuspended)
{
try
{
// Eseguiamo il task asincrono e ASPETTIAMO che finisca
// rimanendo su questo thread.
DoExecTasksAsync().GetAwaiter().GetResult();
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Task completato");
}
catch (Exception ex)
{
lgError("Errore durante i task: " + ex.Message);
}
}
int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF;
if (_errorDelay > 0) currentDelay += _errorDelay;
// Invece di await Task.Delay, usiamo il WaitHandle del CancellationToken
// Questo blocca il thread attuale per X millisecondi o finché non viene cancellato
ct.WaitHandle.WaitOne(currentDelay);
}
}
catch (Exception ex)
{
lgError("Errore fatale nel thread dedicato: " + ex.Message);
}
}
#endif
/// <summary>
/// Loop di gestione worker singolo
/// </summary>
/// <param name="ct"></param>
private void WorkerLoopSingolo(CancellationToken ct)
{
int threadId = Thread.CurrentThread.ManagedThreadId;
lgInfo($"[Thread {threadId}] Loop fisico entrato in esecuzione.");
try
{
while (!ct.IsCancellationRequested)
@@ -2234,7 +2554,75 @@ namespace IOB_WIN_FORM
{
try
{
await DoExecTasks();
// Eseguiamo il lavoro asincrono in modo SINCRONO su questo thread.
// .GetAwaiter().GetResult() blocca questo thread finché il task non è finito.
DoExecTasksAsync(true).GetAwaiter().GetResult();
}
catch (Exception ex)
{
lgError($"Errore nel task: {ex.Message}");
}
}
else
{
// se non fosse stop richiesto da utente...
if (!stopForced)
{
DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec / 2);
// verifica scadenza controllo SE fosse offline x eseguire test riconnessione...
if (iobObj.adpTryRestart && (DateTime.Now > dtVeto) && iobObj.adpRunning && !iobObj.connectionOk)
{
lgTrace($"WorkerLoopAsync sospeso: tentativo riavvio periodico");
lastStartTry = DateTime.Now;
iobObj.tryConnect();
}
else
{
lgTrace($"WorkerLoopAsync sospeso: NON esegue task specifici | _isSuspended: {_isSuspended}");
}
}
}
// Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF
int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF;
// ATTESA: Non rilascia il thread al pool di .NET
// Si interrompe subito se ct viene cancellato
bool cancelled = ct.WaitHandle.WaitOne(currentDelay);
if (cancelled) break;
}
}
catch (Exception ex)
{
lgError($"FATAL: Il thread dedicato {threadId} è morto: {ex.Message}");
}
finally
{
lgInfo($"[Thread {threadId}] Loop fisico terminato.");
}
}
/// <summary>
/// Loop di gestione worker
/// </summary>
/// <param name="ct"></param>
/// <returns></returns>
private async Task WorkerLoopAsync(CancellationToken ct)
{
try
{
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Inizio loop");
while (!ct.IsCancellationRequested)
{
if (!_isSuspended)
{
try
{
int threadIdInt = Thread.CurrentThread.ManagedThreadId;
await DoExecTasksAsync(false);
lgTrace($"[Thread: {threadIdInt}] factory exec DoExecTasksAsync");
}
catch (Exception ex)
{
@@ -2261,13 +2649,24 @@ namespace IOB_WIN_FORM
}
}
// Calcolo del delay dinamico da Timers.MsVHF
// Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF
int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF;
// Il Task.Delay accetta il CancellationToken.
// Se chiudi l'app mentre sta "dormendo", l'attesa si interrompe IMMEDIATAMENTE
// senza dover aspettare la fine del timer, velocizzando la chiusura.
// se ho error delay --> log!
if (_errorDelay > 0)
{
currentDelay += _errorDelay;
lgTrace($"WorkerLoopAsync | delay: {currentDelay}");
}
// 4. ATTESA INTERROMPIBILE
// Il delay asincrono permette al sistema di riutilizzare il thread se necessario,
// ma il 'while' garantisce che non inizieremo il prossimo DoExecTasksAsync prematuramente.
//await Task.Delay(currentDelay, ct).ConfigureAwait(false);
await Task.Delay(currentDelay, ct);
// Logga l'ID: vedrai che ora torna sempre lo stesso!
lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Fine ciclo delay");
}
lgInfo("Worker interrotto per cancellation token.");
}
+1 -24
View File
@@ -1263,13 +1263,7 @@ namespace IOB_WIN_FORM.Iob
return answ;
}
/// <summary>
/// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile
/// </summary>
protected void checkVetoQueueIn()
{
queueInEnabCurr = dtVetoQueueIN < DateTime.Now;
}
/// <summary>
/// Conversione string row in log generico
@@ -3222,24 +3216,7 @@ namespace IOB_WIN_FORM.Iob
url2call = $"{urlRemTask2ExeTav(codTav)}{taskName}";
}
#if false
try
{
Task.Run(async () => answ = await utils.callUrlAsync(url2call))
.GetAwaiter()
.GetResult();
lgInfo($"Task2Exe.remTask2exe | {esitoTask} | chiamata URL {url2call} | answ: {answ}");
}
catch (Exception ex)
{
lgError("ProcessAutoOdl | Crash nel ponte Sync/Async: " + ex.Message);
}
#endif
await utils.callUrlAsync(url2call);
#if false
answ = utils.callUrlNow(url2call);
#endif
}
return answ;
}
+118 -91
View File
@@ -653,12 +653,6 @@ namespace IOB_WIN_FORM.Iob
else
{
currentAnsw = await ExecuteIobCheckWithRetry(maxRetries: 5);
#if false
// 2. Chiamata asincrona con Retry (Ponte Sync/Async)
currentAnsw = Task.Run(async () => await ExecuteIobCheckWithRetry(maxRetries: 5))
.GetAwaiter()
.GetResult();
#endif
}
// 3. Gestione Stato e Logica UI
@@ -698,14 +692,6 @@ namespace IOB_WIN_FORM.Iob
if (dtVetoPing >= DateTime.Now) return MPOnline;
if (DemoOut) return true;
#if false
// 2. Eseguo la parte asincrona forzando l'attesa
// Usiamo Task.Run per far girare il codice asincrono su un thread del pool
// evitando deadlock con il thread della UI
bool isAlive = Task.Run(async () => await ExecuteApiCheckWithRetryAsync(maxRetries: 7))
.GetAwaiter()
.GetResult();
#endif
bool isAlive = await ExecuteApiCheckWithRetryAsync(maxRetries: 7);
@@ -715,6 +701,14 @@ namespace IOB_WIN_FORM.Iob
return isAlive;
}
/// <summary>
/// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile
/// </summary>
public void checkVetoQueueIn()
{
queueInEnabCurr = dtVetoQueueIN < DateTime.Now;
}
/// <summary>
/// Update visualizzaizone BIT in ingresso <paramref name="currDispData">Parametri da
/// aggiornare x display in form</paramref>
@@ -1248,22 +1242,28 @@ namespace IOB_WIN_FORM.Iob
}
/// <summary>
/// effettua recupero dati ed invio valori modificati...
/// Effettua recupero dati ed invio valori modificati...
/// </summary>
/// <param name="ciclo"></param>
public async Task getAndSendAsync(gatherCycle ciclo)
/// <param name="ciclo">Ciclo di freq richiesta</param>
/// <param name="enabPool">Indica se siano abilitate operazioni Pool (Thread Safe)</param>
/// <param name="enabSTID">Indica se siano abilitate le operazioni Single Thread</param>
/// <returns></returns>
public async Task getAndSendAsync(gatherCycle ciclo, bool enabPool, bool enabSTID)
{
// init obj display
newDisplayData currDispData = new newDisplayData();
// IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!!
try
if (enabPool)
{
await TrySendValuesAsync();
}
catch (Exception exc)
{
lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria");
currDispData.semOut = Semaforo.SR;
// IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!!
try
{
await TrySendValuesAsync();
}
catch (Exception exc)
{
lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria");
currDispData.semOut = Semaforo.SR;
}
}
// controllo connessione/connettività
if (connectionOk)
@@ -1293,81 +1293,105 @@ namespace IOB_WIN_FORM.Iob
bool showDebugData = false;
if (ciclo == gatherCycle.VHF)
{
processVHF();
if (enabPool)
{
processVHF();
}
}
// processing dati memoria (lettura, filtraggio, enqueque)
else if (ciclo == gatherCycle.HF)
{
processWhatchDog();
processAllMemory();
if (enabSTID)
{
processWhatchDog();
processAllMemory();
}
}
else if (ciclo == gatherCycle.MF)
{
processMode();
await processServerRequests();
processCustomTaskMF();
processOverride();
processContapezzi();
processCncAlarms();
processDynData();
processMem2Write();
processAllMemory();
if (enabSTID)
{
processMode();
await processServerRequests();
processCustomTaskMF();
processOverride();
processContapezzi();
processCncAlarms();
processDynData();
processMem2Write();
processAllMemory();
}
}
else if (ciclo == gatherCycle.LF)
{
processCustomTaskLF();
await processOtherCounters();
processProgram();
// verifico se devo gestire cambio ODL in modo automatico
await ProcessAutoOdlAsync();
// verifico se devo gestire auto generazione dossier quotidiana
ProcessAutoDossier();
// effettua gestione import file se configurato...
await ProcessFileImportAsync();
// effettua process ritorno ricette
await ProcessRecipeFileRetAsync();
if (enabSTID)
{
processCustomTaskLF();
await processOtherCounters();
processProgram();
}
if (enabPool)
{
// verifico se devo gestire cambio ODL in modo automatico
await ProcessAutoOdlAsync();
// verifico se devo gestire auto generazione dossier quotidiana
ProcessAutoDossier();
// effettua gestione import file se configurato...
await ProcessFileImportAsync();
// effettua process ritorno ricette
await ProcessRecipeFileRetAsync();
}
}
else if (ciclo == gatherCycle.VLF)
{
if (utils.CRB("enableContapezzi"))
if (enabPool)
{
// rilettura contapezzi da server...
lgTrace("Ciclo MsVLF: pzCntReload(true)");
if (!isMulti)
if (utils.CRB("enableContapezzi"))
{
pzCntReload(true);
// rilettura contapezzi da server...
lgTrace("Ciclo MsVLF: pzCntReload(true)");
if (!isMulti)
{
pzCntReload(true);
}
// refresh associazione Macchina - IOB
await SendM2IobAsync();
// invio altri dati accessori...
await SendMachineConfAsync();
}
// refresh associazione Macchina - IOB
await SendM2IobAsync();
// invio altri dati accessori...
await SendMachineConfAsync();
}
// recupero dati SETUP (sysinfo) e li invio/mostro se variati...
processSysInfo();
// checkLogDir x shrink!
checkShrinkDir();
// eventuale log!
if (utils.CRB("recTime"))
{
try
// checkLogDir x shrink!
checkShrinkDir();
// eventuale log!
if (utils.CRB("recTime"))
{
logTimeResults();
try
{
logTimeResults();
}
catch
{ }
}
catch
{ }
processRecipeSyncArch();
}
processRecipeSyncArch();
if (enableSlowData)
if (enabSTID)
{
processSlowDataRead();
// recupero dati SETUP (sysinfo) e li invio/mostro se variati...
processSysInfo();
if (enableSlowData)
{
processSlowDataRead();
}
}
}
// mostra eventuali altri dati di processo...
reportDataProc();
if (showDebugData)
if (enabPool)
{
// verifica se debba salvare e mostrare dati
checkSavePersDataLayer();
// mostra eventuali altri dati di processo...
reportDataProc();
if (showDebugData)
{
// verifica se debba salvare e mostrare dati
checkSavePersDataLayer();
}
}
}
catch (Exception exc)
@@ -1402,21 +1426,24 @@ namespace IOB_WIN_FORM.Iob
}
else
{
// anche se NON connesso alcuni task di bassa freq li eseguo...
if (ciclo == gatherCycle.LF)
if (enabPool)
{
// verifico se devo gestire cambio ODL in modo automatico
await ProcessAutoOdlAsync();
// verifico se devo gestire auto generazione dossier quotidiana
ProcessAutoDossier();
// effettua gestione import file se configurato...
await ProcessFileImportAsync();
// effettua process ritorno ricette
await ProcessRecipeFileRetAsync();
}
else if (ciclo == gatherCycle.VLF)
{
processRecipeSyncArch();
// anche se NON connesso alcuni task di bassa freq li eseguo...
if (ciclo == gatherCycle.LF)
{
// verifico se devo gestire cambio ODL in modo automatico
await ProcessAutoOdlAsync();
// verifico se devo gestire auto generazione dossier quotidiana
ProcessAutoDossier();
// effettua gestione import file se configurato...
await ProcessFileImportAsync();
// effettua process ritorno ricette
await ProcessRecipeFileRetAsync();
}
else if (ciclo == gatherCycle.VLF)
{
processRecipeSyncArch();
}
}
// provo a riconnettere SE abilitato tryRestart...