From 8cc16143defa1cea0022fbed3ee31ce76fcbf115 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Wed, 6 Feb 2019 07:00:27 +0000 Subject: [PATCH] Cleaned and added a base class for view models with background processing tha uses the new ThreadedAction class to do its background processing; added a view binding interface for WinForms that emulates the WPF design git-svn-id: file:///srv/devel/repo-conversion/nusu@334 d2e56fa2-650e-0410-a79f-9358c0239efd --- Source/Threading/ThreadRunner.cs | 794 +++++++++++++++---------------- 1 file changed, 397 insertions(+), 397 deletions(-) diff --git a/Source/Threading/ThreadRunner.cs b/Source/Threading/ThreadRunner.cs index d0b7cf7..b86e9ab 100644 --- a/Source/Threading/ThreadRunner.cs +++ b/Source/Threading/ThreadRunner.cs @@ -28,433 +28,433 @@ using System.Threading.Tasks; namespace Nuclex.Support.Threading { - /// Executes actions in a threads - public abstract class ThreadRunner : IDisposable { + /// Executes actions in a threads + public abstract class ThreadRunner : IDisposable { - #region interface IRunner + #region interface IRunner - /// Interface for a background task runner - private interface IRunner { + /// Interface for a background task runner + private interface IRunner { - /// Runs the background task - void Run(); + /// Runs the background task + void Run(); - /// The runner's cancellation token source - CancellationTokenSource CancellationTokenSource { get; } + /// The runner's cancellation token source + CancellationTokenSource CancellationTokenSource { get; } - } + } - #endregion // interface IRunner + #endregion // interface IRunner - #region struct Runner + #region struct Runner - /// Runs a background task with no parameters - private struct Runner : IRunner { + /// Runs a background task with no parameters + private struct Runner : IRunner { - /// Initializes a new runner - /// Action the runner will execute - public Runner(Action action) { - this.action = action; - } + /// Initializes a new runner + /// Action the runner will execute + public Runner(Action action) { + this.action = action; + } - /// Executes the runner's action - public void Run() { - this.action(); - } + /// Executes the runner's action + public void Run() { + this.action(); + } - /// The runner's cancellation token source - public CancellationTokenSource CancellationTokenSource { - get { return null; } - } + /// The runner's cancellation token source + public CancellationTokenSource CancellationTokenSource { + get { return null; } + } - /// Action the runner will execute - private Action action; + /// Action the runner will execute + private Action action; - } + } - #endregion // struct Runner + #endregion // struct Runner - #region struct CancellableRunner + #region struct CancellableRunner - /// Runs a background task with no parameters - private struct CancellableRunner : IRunner { + /// Runs a background task with no parameters + private struct CancellableRunner : IRunner { - /// Initializes a new runner - /// Action the runner will execute - public CancellableRunner(CancellableAction action) { - this.action = action; - this.cancellationTokenSource = new CancellationTokenSource(); - } + /// Initializes a new runner + /// Action the runner will execute + public CancellableRunner(CancellableAction action) { + this.action = action; + this.cancellationTokenSource = new CancellationTokenSource(); + } - /// Executes the runner's action - public void Run() { - this.action(this.cancellationTokenSource.Token); - } + /// Executes the runner's action + public void Run() { + this.action(this.cancellationTokenSource.Token); + } - /// The runner's cancellation token source - public CancellationTokenSource CancellationTokenSource { - get { return this.cancellationTokenSource; } - } + /// The runner's cancellation token source + public CancellationTokenSource CancellationTokenSource { + get { return this.cancellationTokenSource; } + } - /// The runner's cancellation token source - private CancellationTokenSource cancellationTokenSource; - /// Action the runner will execute - private CancellableAction action; + /// The runner's cancellation token source + private CancellationTokenSource cancellationTokenSource; + /// Action the runner will execute + private CancellableAction action; - } + } - #endregion // struct CancellableRunner + #endregion // struct CancellableRunner - #region struct Runner + #region struct Runner - /// Runs a background task with one parameter - private struct Runner : IRunner { + /// Runs a background task with one parameter + private struct Runner : IRunner { - /// Initializes a new runner - /// Action the runner will execute - /// Parameter that will be passed to the action - public Runner(Action action, P1 parameter1) { - this.action = action; - this.parameter1 = parameter1; - } + /// Initializes a new runner + /// Action the runner will execute + /// Parameter that will be passed to the action + public Runner(Action action, P1 parameter1) { + this.action = action; + this.parameter1 = parameter1; + } - /// Executes the runner's action - public void Run() { - this.action(this.parameter1); - } + /// Executes the runner's action + public void Run() { + this.action(this.parameter1); + } - /// The runner's cancellation token source - public CancellationTokenSource CancellationTokenSource { - get { return null; } - } + /// The runner's cancellation token source + public CancellationTokenSource CancellationTokenSource { + get { return null; } + } - /// Action the runner will execute - private Action action; - /// Parameter that will be passed to the action - private P1 parameter1; + /// Action the runner will execute + private Action action; + /// Parameter that will be passed to the action + private P1 parameter1; - } + } - #endregion // struct Runner - - #region struct CancellableRunner - - /// Runs a background task with one parameter - private struct CancellableRunner : IRunner { - - /// Initializes a new runner - /// Action the runner will execute - /// Parameter that will be passed to the action - public CancellableRunner(CancellableAction action, P1 parameter1) { - this.action = action; - this.parameter1 = parameter1; - this.cancellationTokenSource = new CancellationTokenSource(); - } - - /// Executes the runner's action - public void Run() { - this.action(this.parameter1, this.cancellationTokenSource.Token); - } - - /// The runner's cancellation token source - public CancellationTokenSource CancellationTokenSource { - get { return this.cancellationTokenSource; } - } - - /// The runner's cancellation token source - private CancellationTokenSource cancellationTokenSource; - /// Action the runner will execute - private CancellableAction action; - /// Parameter that will be passed to the action - private P1 parameter1; - - } - - #endregion // struct CancellableRunner - - #region struct Runner - - /// Runs a background task with one parameter - private struct Runner : IRunner { - - /// Initializes a new runner - /// Action the runner will execute - /// First parameter that will be passed to the action - /// Second parameter that will be passed to the action - public Runner(Action action, P1 parameter1, P2 parameter2) { - this.action = action; - this.parameter1 = parameter1; - this.parameter2 = parameter2; - } - - /// Executes the runner's action - public void Run() { - this.action(this.parameter1, this.parameter2); - } - - /// The runner's cancellation token source - public CancellationTokenSource CancellationTokenSource { - get { return null; } - } - - /// Action the runner will execute - private Action action; - /// First parameter that will be passed to the action - private P1 parameter1; - /// Second parameter that will be passed to the action - private P2 parameter2; - - } - - #endregion // struct Runner - - #region struct CancellableRunner - - /// Runs a background task with one parameter - private struct CancellableRunner : IRunner { - - /// Initializes a new runner - /// Action the runner will execute - /// First parameter that will be passed to the action - /// Second parameter that will be passed to the action - public CancellableRunner(CancellableAction action, P1 parameter1, P2 parameter2) { - this.action = action; - this.parameter1 = parameter1; - this.parameter2 = parameter2; - this.cancellationTokenSource = new CancellationTokenSource(); - } - - /// Executes the runner's action - public void Run() { - this.action(this.parameter1, this.parameter2, this.cancellationTokenSource.Token); - } - - /// The runner's cancellation token source - public CancellationTokenSource CancellationTokenSource { - get { return this.cancellationTokenSource; } - } - - /// The runner's cancellation token source - private CancellationTokenSource cancellationTokenSource; - /// Action the runner will execute - private CancellableAction action; - /// First parameter that will be passed to the action - private P1 parameter1; - /// Second parameter that will be passed to the action - private P2 parameter2; - - } - - #endregion // struct CancellableRunner - - /// Initializes a new background processing handler - public ThreadRunner() { - this.executeQueuedRunnersInThreadDelegate = new Action(executeQueuedRunnersInThread); - this.queuedRunners = new ConcurrentQueue(); - } - - /// - /// Immediately cancels all operations and releases the resources used by the instance - /// - public void Dispose() { - Dispose(timeoutMilliseconds: 2500); - } - - /// - /// Immediately cancels all operations and releases the resources used by the instance - /// - /// - /// Time to wait for the background tasks before dropping the tasks unfinished - /// - public void Dispose(int timeoutMilliseconds) { - CancelAllBackgroundOperations(); - - Task currentTask; - lock(this) { - currentTask = this.currentTask; - } - - if(currentTask != null) { - if(!currentTask.Wait(timeoutMilliseconds)) { - Debug.Assert(false, "Task does not support cancellation or did not cancel in time"); - } - lock(this) { - this.currentTask = null; - IsBusy = false; - } - } - } - - /// Whether the view model is currently busy executing a task - public bool IsBusy { - get { return this.isBusy; } - private set { - if(value != this.isBusy) { - this.isBusy = value; - BusyChanged(); - } - } - } - - /// Reports an error - /// Error that will be reported - protected abstract void ReportError(Exception exception); - - /// Called when the status of the busy flag changes - protected abstract void BusyChanged(); - - /// Executes the specified operation in the background - /// Action that will be executed in the background - public void RunInBackground(Action action) { - this.queuedRunners.Enqueue(new Runner(action)); - startBackgroundProcessingIfNecessary(); - } - - /// Executes the specified operation in the background - /// Action that will be executed in the background - public void RunInBackground(CancellableAction action) { - this.queuedRunners.Enqueue(new CancellableRunner(action)); - startBackgroundProcessingIfNecessary(); - } - - /// Executes the specified operation in the background - /// Action that will be executed in the background - /// Parameter that will be passed to the action - public void RunInBackground(Action action, P1 parameter1) { - this.queuedRunners.Enqueue(new Runner(action, parameter1)); - startBackgroundProcessingIfNecessary(); - } - - /// Executes the specified operation in the background - /// Action that will be executed in the background - /// Parameter that will be passed to the action - public void RunInBackground(CancellableAction action, P1 parameter1) { - this.queuedRunners.Enqueue(new CancellableRunner(action, parameter1)); - startBackgroundProcessingIfNecessary(); - } - - /// Executes the specified operation in the background - /// Action that will be executed in the background - /// First parameter that will be passed to the action - /// Second parameter that will be passed to the action - public void RunInBackground(Action action, P1 parameter1, P2 parameter2) { - this.queuedRunners.Enqueue(new Runner(action, parameter1, parameter2)); - startBackgroundProcessingIfNecessary(); - } - - /// Executes the specified operation in the background - /// Action that will be executed in the background - /// First parameter that will be passed to the action - /// Second parameter that will be passed to the action - public void RunInBackground( - CancellableAction action, P1 parameter1, P2 parameter2 - ) { - this.queuedRunners.Enqueue(new CancellableRunner(action, parameter1, parameter2)); - startBackgroundProcessingIfNecessary(); - } - - /// Cancels the currently running background operation - public void CancelBackgroundOperation() { - IRunner currentRunner = this.currentRunner; - if(currentRunner != null) { - CancellationTokenSource cancellationTokenSource = currentRunner.CancellationTokenSource; - if(cancellationTokenSource != null) { - cancellationTokenSource.Cancel(); - } - } - } - - /// Cancels all queued and the currently running background operation - public void CancelAllBackgroundOperations() { - IRunner runner; - while(this.queuedRunners.TryDequeue(out runner)) { - CancellationTokenSource cancellationTokenSource = runner.CancellationTokenSource; - if(cancellationTokenSource != null) { - cancellationTokenSource.Cancel(); - } - } - - CancelBackgroundOperation(); - } - - /// Whether the background operation has been cancelled - //[Obsolete("Please use a method accepting a cancellation token instead of using this")] - public bool IsBackgroundOperationCancelled { - get { - IRunner currentRunner = this.currentRunner; - if(currentRunner != null) { - return currentRunner.CancellationTokenSource.IsCancellationRequested; - } else { - return false; - } - } - } - - /// Throws an exception if the background operation was cancelled - //[Obsolete("Please use a method accepting a cancellation token instead of using this")] - public void ThrowIfBackgroundOperationCancelled() { - IRunner currentRunner = this.currentRunner; - if(currentRunner != null) { - currentRunner.CancellationTokenSource.Token.ThrowIfCancellationRequested(); - } - } - - /// Executes the queued runners in the background - private void executeQueuedRunnersInThread() { - IsBusy = true; - - IRunner runner; - while(this.queuedRunners.TryDequeue(out runner)) { - try { - this.currentRunner = runner; - runner.Run(); - } - catch(OperationCanceledException) { - // Ignore - } - catch(Exception exception) { - this.currentRunner = null; // When the error is reported this should already be null - ReportError(exception); - } - this.currentRunner = null; - } - - lock(this) { - this.currentTask = null; - IsBusy = false; - } - } - - /// Starts the background processing thread, if needed - private void startBackgroundProcessingIfNecessary() { - Task currentTask; - - lock(this) { - if(this.currentTask == null) { - currentTask = new Task(this.executeQueuedRunnersInThreadDelegate); - this.currentTask = currentTask; - } else { - return; // Task is already running - } - } - - // Start the task outside of the lock statement so that when the thread starts to run, - // it is guaranteed to read the currentTask variable as the task we just created. - currentTask.Start(); - } - - /// Whether the view model is currently busy executing a task - private volatile bool isBusy; - /// Delegate for the executedQueuedRunnersInThread() method - private Action executeQueuedRunnersInThreadDelegate; - /// Queued background operations - private ConcurrentQueue queuedRunners; - /// Runner currently executing in the background - private volatile IRunner currentRunner; - /// Task that is currently executing the runners - private Task currentTask; - - } + #endregion // struct Runner + + #region struct CancellableRunner + + /// Runs a background task with one parameter + private struct CancellableRunner : IRunner { + + /// Initializes a new runner + /// Action the runner will execute + /// Parameter that will be passed to the action + public CancellableRunner(CancellableAction action, P1 parameter1) { + this.action = action; + this.parameter1 = parameter1; + this.cancellationTokenSource = new CancellationTokenSource(); + } + + /// Executes the runner's action + public void Run() { + this.action(this.parameter1, this.cancellationTokenSource.Token); + } + + /// The runner's cancellation token source + public CancellationTokenSource CancellationTokenSource { + get { return this.cancellationTokenSource; } + } + + /// The runner's cancellation token source + private CancellationTokenSource cancellationTokenSource; + /// Action the runner will execute + private CancellableAction action; + /// Parameter that will be passed to the action + private P1 parameter1; + + } + + #endregion // struct CancellableRunner + + #region struct Runner + + /// Runs a background task with one parameter + private struct Runner : IRunner { + + /// Initializes a new runner + /// Action the runner will execute + /// First parameter that will be passed to the action + /// Second parameter that will be passed to the action + public Runner(Action action, P1 parameter1, P2 parameter2) { + this.action = action; + this.parameter1 = parameter1; + this.parameter2 = parameter2; + } + + /// Executes the runner's action + public void Run() { + this.action(this.parameter1, this.parameter2); + } + + /// The runner's cancellation token source + public CancellationTokenSource CancellationTokenSource { + get { return null; } + } + + /// Action the runner will execute + private Action action; + /// First parameter that will be passed to the action + private P1 parameter1; + /// Second parameter that will be passed to the action + private P2 parameter2; + + } + + #endregion // struct Runner + + #region struct CancellableRunner + + /// Runs a background task with one parameter + private struct CancellableRunner : IRunner { + + /// Initializes a new runner + /// Action the runner will execute + /// First parameter that will be passed to the action + /// Second parameter that will be passed to the action + public CancellableRunner(CancellableAction action, P1 parameter1, P2 parameter2) { + this.action = action; + this.parameter1 = parameter1; + this.parameter2 = parameter2; + this.cancellationTokenSource = new CancellationTokenSource(); + } + + /// Executes the runner's action + public void Run() { + this.action(this.parameter1, this.parameter2, this.cancellationTokenSource.Token); + } + + /// The runner's cancellation token source + public CancellationTokenSource CancellationTokenSource { + get { return this.cancellationTokenSource; } + } + + /// The runner's cancellation token source + private CancellationTokenSource cancellationTokenSource; + /// Action the runner will execute + private CancellableAction action; + /// First parameter that will be passed to the action + private P1 parameter1; + /// Second parameter that will be passed to the action + private P2 parameter2; + + } + + #endregion // struct CancellableRunner + + /// Initializes a new background processing handler + public ThreadRunner() { + this.executeQueuedRunnersInThreadDelegate = new Action(executeQueuedRunnersInThread); + this.queuedRunners = new ConcurrentQueue(); + } + + /// + /// Immediately cancels all operations and releases the resources used by the instance + /// + public void Dispose() { + Dispose(timeoutMilliseconds: 2500); + } + + /// + /// Immediately cancels all operations and releases the resources used by the instance + /// + /// + /// Time to wait for the background tasks before dropping the tasks unfinished + /// + public void Dispose(int timeoutMilliseconds) { + CancelAllBackgroundOperations(); + + Task currentTask; + lock(this) { + currentTask = this.currentTask; + } + + if(currentTask != null) { + if(!currentTask.Wait(timeoutMilliseconds)) { + Debug.Assert(false, "Task does not support cancellation or did not cancel in time"); + } + lock(this) { + this.currentTask = null; + IsBusy = false; + } + } + } + + /// Whether the view model is currently busy executing a task + public bool IsBusy { + get { return this.isBusy; } + private set { + if(value != this.isBusy) { + this.isBusy = value; + BusyChanged(); + } + } + } + + /// Reports an error + /// Error that will be reported + protected abstract void ReportError(Exception exception); + + /// Called when the status of the busy flag changes + protected abstract void BusyChanged(); + + /// Executes the specified operation in the background + /// Action that will be executed in the background + public void RunInBackground(Action action) { + this.queuedRunners.Enqueue(new Runner(action)); + startBackgroundProcessingIfNecessary(); + } + + /// Executes the specified operation in the background + /// Action that will be executed in the background + public void RunInBackground(CancellableAction action) { + this.queuedRunners.Enqueue(new CancellableRunner(action)); + startBackgroundProcessingIfNecessary(); + } + + /// Executes the specified operation in the background + /// Action that will be executed in the background + /// Parameter that will be passed to the action + public void RunInBackground(Action action, P1 parameter1) { + this.queuedRunners.Enqueue(new Runner(action, parameter1)); + startBackgroundProcessingIfNecessary(); + } + + /// Executes the specified operation in the background + /// Action that will be executed in the background + /// Parameter that will be passed to the action + public void RunInBackground(CancellableAction action, P1 parameter1) { + this.queuedRunners.Enqueue(new CancellableRunner(action, parameter1)); + startBackgroundProcessingIfNecessary(); + } + + /// Executes the specified operation in the background + /// Action that will be executed in the background + /// First parameter that will be passed to the action + /// Second parameter that will be passed to the action + public void RunInBackground(Action action, P1 parameter1, P2 parameter2) { + this.queuedRunners.Enqueue(new Runner(action, parameter1, parameter2)); + startBackgroundProcessingIfNecessary(); + } + + /// Executes the specified operation in the background + /// Action that will be executed in the background + /// First parameter that will be passed to the action + /// Second parameter that will be passed to the action + public void RunInBackground( + CancellableAction action, P1 parameter1, P2 parameter2 + ) { + this.queuedRunners.Enqueue(new CancellableRunner(action, parameter1, parameter2)); + startBackgroundProcessingIfNecessary(); + } + + /// Cancels the currently running background operation + public void CancelBackgroundOperation() { + IRunner currentRunner = this.currentRunner; + if(currentRunner != null) { + CancellationTokenSource cancellationTokenSource = currentRunner.CancellationTokenSource; + if(cancellationTokenSource != null) { + cancellationTokenSource.Cancel(); + } + } + } + + /// Cancels all queued and the currently running background operation + public void CancelAllBackgroundOperations() { + IRunner runner; + while(this.queuedRunners.TryDequeue(out runner)) { + CancellationTokenSource cancellationTokenSource = runner.CancellationTokenSource; + if(cancellationTokenSource != null) { + cancellationTokenSource.Cancel(); + } + } + + CancelBackgroundOperation(); + } + + /// Whether the background operation has been cancelled + //[Obsolete("Please use a method accepting a cancellation token instead of using this")] + public bool IsBackgroundOperationCancelled { + get { + IRunner currentRunner = this.currentRunner; + if(currentRunner != null) { + return currentRunner.CancellationTokenSource.IsCancellationRequested; + } else { + return false; + } + } + } + + /// Throws an exception if the background operation was cancelled + //[Obsolete("Please use a method accepting a cancellation token instead of using this")] + public void ThrowIfBackgroundOperationCancelled() { + IRunner currentRunner = this.currentRunner; + if(currentRunner != null) { + currentRunner.CancellationTokenSource.Token.ThrowIfCancellationRequested(); + } + } + + /// Executes the queued runners in the background + private void executeQueuedRunnersInThread() { + IsBusy = true; + + IRunner runner; + while(this.queuedRunners.TryDequeue(out runner)) { + try { + this.currentRunner = runner; + runner.Run(); + } + catch(OperationCanceledException) { + // Ignore + } + catch(Exception exception) { + this.currentRunner = null; // When the error is reported this should already be null + ReportError(exception); + } + this.currentRunner = null; + } + + lock(this) { + this.currentTask = null; + IsBusy = false; + } + } + + /// Starts the background processing thread, if needed + private void startBackgroundProcessingIfNecessary() { + Task currentTask; + + lock(this) { + if(this.currentTask == null) { + currentTask = new Task(this.executeQueuedRunnersInThreadDelegate); + this.currentTask = currentTask; + } else { + return; // Task is already running + } + } + + // Start the task outside of the lock statement so that when the thread starts to run, + // it is guaranteed to read the currentTask variable as the task we just created. + currentTask.Start(); + } + + /// Whether the view model is currently busy executing a task + private volatile bool isBusy; + /// Delegate for the executedQueuedRunnersInThread() method + private Action executeQueuedRunnersInThreadDelegate; + /// Queued background operations + private ConcurrentQueue queuedRunners; + /// Runner currently executing in the background + private volatile IRunner currentRunner; + /// Task that is currently executing the runners + private Task currentTask; + + } } // namespace Nuclex.Support.Threading