Codex introduction. Base process tidy.

This commit is contained in:
Andy Howard
2026-09-10 09:35:05 +01:00
parent c2020c8302
commit c2a8757ebf
10 changed files with 466 additions and 63 deletions
+104 -62
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Tasks;
using Sarand.Net.Standard.Base.Utils;
@@ -178,6 +178,12 @@ namespace Sarand.Net.Standard.Base.Procs
// Task control data.
private SacsTaskData taskData = null;
public SacsTaskData TaskData { get { return taskData; } set { if (taskData != null) taskData.Dispose(); taskData = value; } }
public bool DetachTaskData(SacsTaskData expected)
{
if (!Object.ReferenceEquals(taskData, expected)) return false;
taskData = null;
return true;
}
}
//-------------------------------------------------------------------
@@ -195,8 +201,15 @@ namespace Sarand.Net.Standard.Base.Procs
{
if (disposing)
{
// Clear process data to dispose.
Control.TaskData = null;
AsyncProcessData asyncData;
lock (AsyncLock)
{
asyncData = AsyncData;
if (asyncData == null)
Control.TaskData = null;
}
// A running worker owns its task data until it exits.
asyncData?.RequestCancellation();
}
}
@@ -230,65 +243,96 @@ namespace Sarand.Net.Standard.Base.Procs
// Run process asynchronously.
private class AsyncProcessData : SacsDisposable
{
public AsyncProcessData(Task task)
private readonly Object CancellationLock = new object();
public AsyncProcessData(SacsTaskData taskData)
{
this.Task = task;
this.TaskData = taskData;
}
protected override void Dispose(bool disposing)
{
if (disposing) { }
}
public Task Task { get; }
}
private AsyncProcessData AsyncData { get; set; }
public bool IsAsync { get { return AsyncData != null; } }
private void ClearAsyncData()
{
if (AsyncData != null)
{
lock (AsyncData)
if (disposing)
{
AsyncData.Dispose();
AsyncData = null;
lock (CancellationLock)
TaskData.Dispose();
}
}
public Task Task { get; set; }
public SacsTaskData TaskData { get; }
public bool RequestCancellation()
{
lock (CancellationLock)
return TaskData.TriggerCancellation();
}
}
private readonly Object AsyncLock = new object();
private AsyncProcessData AsyncData { get; set; }
public bool IsAsync { get { lock (AsyncLock) { return AsyncData != null; } } }
private void ClearAsyncData(AsyncProcessData asyncData)
{
bool clear = false;
lock (AsyncLock)
{
// Only the task that owns the current state may clear it.
if (Object.ReferenceEquals(AsyncData, asyncData))
{
Control.DetachTaskData(asyncData.TaskData);
AsyncData = null;
clear = true;
}
}
// Dispose outside the lifecycle lock. Disposal waits for any cancellation callback.
if (clear)
asyncData.Dispose();
}
/// <summary>
/// Run the process asynchronously.
/// </summary>
/// <returns></returns>
/// <exception cref="InProgressException">If process is already running.</exception>
public async void RunAsync()
public async Task RunAsync()
{
if (AsyncData != null)
throw new InProgressException();
Control.TaskData = new SacsTaskData();
// Run on separate thread.
Task task = new Task(() =>
AsyncProcessData asyncData;
lock (AsyncLock)
{
try
if (AsyncData != null)
throw new InProgressException();
SacsTaskData taskData = new SacsTaskData();
Control.TaskData = taskData;
// Construct before starting so the process state is visible atomically.
asyncData = new AsyncProcessData(taskData);
Task task = new Task(() =>
{
// Execute the process.
RunProcess();
}
catch (OperationCanceledException exc)
{
SaLogger.Warn(exc.GetType().Name + " during asynchronous processing on " + GetType().Name);
}
finally
{
// Clear processing data.
ClearAsyncData();
}
}, Control.TaskData.CancellationToken);
AsyncData = new AsyncProcessData(task);
task.Start();
await task;
try
{
// Execute the process.
RunProcess(false);
}
catch (OperationCanceledException exc)
{
SaLogger.Warn(exc.GetType().Name + " during asynchronous processing on " + GetType().Name);
}
finally
{
// The worker owns its state until it has actually terminated.
ClearAsyncData(asyncData);
}
}, taskData.CancellationToken);
asyncData.Task = task;
AsyncData = asyncData;
task.Start();
}
await asyncData.Task;
}
//-------------------------------------------------------------------
// Run process inline.
public bool RunProcess()
{
return RunProcess(true);
}
private bool RunProcess(bool clearTaskData)
{
// Prepare process.
bool runProcess = false;
@@ -329,7 +373,8 @@ namespace Sarand.Net.Standard.Base.Procs
FinishedAt = DateTime.Now;
Processing = false;
ProcessEnded = true;
Control.TaskData = null;
if (clearTaskData)
Control.TaskData = null;
}
// Process completed.
@@ -349,28 +394,25 @@ namespace Sarand.Net.Standard.Base.Procs
protected abstract bool Run();
//-------------------------------------------------------------------
public async Task StopProcessAsync(int timeout = 50)
/// <returns>True if no process is running or the process stopped within the timeout.</returns>
public async Task<bool> StopProcessAsync(int timeout = 50)
{
if (!Processing) return;
if (AsyncData != null)
AsyncProcessData asyncData;
lock (AsyncLock)
asyncData = AsyncData;
if (asyncData == null)
return true;
// Cancellation may invoke callbacks synchronously, so request it outside AsyncLock.
// Cancellation is cooperative; retain the process state until the worker exits.
asyncData.RequestCancellation();
if (await Task.WhenAny(asyncData.Task, Task.Delay(Math.Max(20, timeout))) != asyncData.Task)
{
try
{
// Cancel the task.
Control.TaskData.TriggerCancellation();
// Wait for task termination or timeout.
if (await Task.WhenAny(AsyncData.Task, Task.Delay(Math.Max(20, Math.Min(1000, timeout)))) != AsyncData.Task)
{
// Timeout completed before task - force termination.
Control.TaskData.ThrowIfCancellationRequested();
}
}
finally
{
Control.TaskData = null;
ClearAsyncData();
}
return false;
}
await asyncData.Task;
return true;
}
//-------------------------------------------------------------------
@@ -499,7 +541,7 @@ namespace Sarand.Net.Standard.Base.Procs
// Interruption.
public bool IsInterrupted
{
get { return Control.Interrupted; }
get { return Control.Interrupted || (Control.TaskData?.IsCancellationRequested ?? false); }
}
public bool Interrupt()
{