diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index de2f3035..4c2f5b19 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -62,8 +62,12 @@ + + + + diff --git a/ModuleManager/Threading/BackgroundTask.cs b/ModuleManager/Threading/BackgroundTask.cs new file mode 100644 index 00000000..64891c04 --- /dev/null +++ b/ModuleManager/Threading/BackgroundTask.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading; + +namespace ModuleManager.Threading +{ + public static class BackgroundTask + { + public static ITaskStatus Start(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + TaskStatus status = new TaskStatus(); + + void RunAction() + { + try + { + action(); + status.Finished(); + } + catch (Exception ex) + { + status.Error(ex); + } + } + + Thread thread = new Thread(RunAction); + thread.Start(); + + return new TaskStatusWrapper(status); + } + } +} diff --git a/ModuleManager/Threading/ITaskStatus.cs b/ModuleManager/Threading/ITaskStatus.cs new file mode 100644 index 00000000..e60c76bd --- /dev/null +++ b/ModuleManager/Threading/ITaskStatus.cs @@ -0,0 +1,12 @@ +using System; + +namespace ModuleManager.Threading +{ + public interface ITaskStatus + { + bool IsRunning { get; } + bool IsFinished { get; } + bool IsExitedWithError { get; } + Exception Exception { get; } + } +} diff --git a/ModuleManager/Threading/TaskStatus.cs b/ModuleManager/Threading/TaskStatus.cs new file mode 100644 index 00000000..92f8c237 --- /dev/null +++ b/ModuleManager/Threading/TaskStatus.cs @@ -0,0 +1,55 @@ +using System; + +namespace ModuleManager.Threading +{ + public class TaskStatus : ITaskStatus + { + private bool isRunning = true; + private Exception exception = null; + private object lockObject = new object(); + + public bool IsRunning => isRunning; + public Exception Exception => exception; + + public bool IsFinished + { + get + { + lock (lockObject) + { + return !isRunning && exception == null; + } + } + } + + public bool IsExitedWithError + { + get + { + lock (lockObject) + { + return !isRunning && exception != null; + } + } + } + + public void Finished() + { + lock (lockObject) + { + if (!isRunning) throw new InvalidOperationException("Task is not running"); + isRunning = false; + } + } + + public void Error(Exception exception) + { + lock(lockObject) + { + if (!isRunning) throw new InvalidOperationException("Task is not running"); + this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); + isRunning = false; + } + } + } +} diff --git a/ModuleManager/Threading/TaskStatusWrapper.cs b/ModuleManager/Threading/TaskStatusWrapper.cs new file mode 100644 index 00000000..eff24f78 --- /dev/null +++ b/ModuleManager/Threading/TaskStatusWrapper.cs @@ -0,0 +1,19 @@ +using System; + +namespace ModuleManager.Threading +{ + public class TaskStatusWrapper : ITaskStatus + { + private ITaskStatus inner; + + public TaskStatusWrapper(ITaskStatus inner) + { + this.inner = inner; + } + + public bool IsRunning => inner.IsRunning; + public bool IsFinished => inner.IsFinished; + public bool IsExitedWithError => inner.IsExitedWithError; + public Exception Exception => inner.Exception; + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 03ec31ff..bd5c52f6 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -67,6 +67,8 @@ + + diff --git a/ModuleManagerTests/Threading/BackgroundTaskTest.cs b/ModuleManagerTests/Threading/BackgroundTaskTest.cs new file mode 100644 index 00000000..c9ebc594 --- /dev/null +++ b/ModuleManagerTests/Threading/BackgroundTaskTest.cs @@ -0,0 +1,72 @@ +using System; +using Xunit; +using ModuleManager.Threading; + +namespace ModuleManagerTests.Threading +{ + public class BackgroundTaskTest + { + [Fact] + public void Test__Start() + { + bool finish = false; + void Run() + { + while (!finish) continue; + } + + ITaskStatus status = BackgroundTask.Start(Run); + + Assert.True(status.IsRunning); + Assert.False(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + + finish = true; + + while (status.IsRunning) continue; + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void Test__Start__Exception() + { + bool finish = false; + Exception ex = new Exception(); + void Run() + { + while (!finish) continue; + throw ex; + } + + ITaskStatus status = BackgroundTask.Start(Run); + + Assert.True(status.IsRunning); + Assert.False(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + + finish = true; + + while (status.IsRunning) continue; + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + + [Fact] + public void Test__Run__ArgumentNull() + { + Assert.Throws(delegate + { + BackgroundTask.Start(null); + }); + } + } +} diff --git a/ModuleManagerTests/Threading/TaskStatusTest.cs b/ModuleManagerTests/Threading/TaskStatusTest.cs new file mode 100644 index 00000000..a32983ff --- /dev/null +++ b/ModuleManagerTests/Threading/TaskStatusTest.cs @@ -0,0 +1,121 @@ +using System; +using Xunit; +using ModuleManager.Threading; + +namespace ModuleManagerTests.Threading +{ + public class TaskStatusTest + { + [Fact] + public void Test__Cosntructor() + { + TaskStatus status = new TaskStatus(); + + Assert.True(status.IsRunning); + Assert.False(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestFinished() + { + TaskStatus status = new TaskStatus(); + + status.Finished(); + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestError() + { + TaskStatus status = new TaskStatus(); + Exception ex = new Exception(); + + status.Error(ex); + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + + [Fact] + public void TestFinished__AlreadyFinished() + { + TaskStatus status = new TaskStatus(); + + status.Finished(); + + Assert.Throws(delegate + { + status.Finished(); + }); + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestFinished__AlreadyErrored() + { + TaskStatus status = new TaskStatus(); + Exception ex = new Exception(); + + status.Error(ex); + + Assert.Throws(delegate + { + status.Finished(); + }); + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + + [Fact] + public void TestError__AlreadyFinished() + { + TaskStatus status = new TaskStatus(); + + status.Finished(); + + Assert.Throws(delegate + { + status.Error(new Exception()); + }); + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestError__AlreadyErrored() + { + TaskStatus status = new TaskStatus(); + Exception ex = new Exception(); + + status.Error(ex); + + Assert.Throws(delegate + { + status.Error(new Exception()); + }); + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + } +}