Codex introduction. Base process tidy.
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user