Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allows a background task to be run and monitored, including if it exits due to an exception
- Loading branch information
1 parent
78d4635
commit 172e2f3
Showing
8 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System; | ||
|
||
namespace ModuleManager.Threading | ||
{ | ||
public interface ITaskStatus | ||
{ | ||
bool IsRunning { get; } | ||
bool IsFinished { get; } | ||
bool IsExitedWithError { get; } | ||
Exception Exception { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ArgumentNullException>(delegate | ||
{ | ||
BackgroundTask.Start(null); | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<InvalidOperationException>(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<InvalidOperationException>(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<InvalidOperationException>(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<InvalidOperationException>(delegate | ||
{ | ||
status.Error(new Exception()); | ||
}); | ||
|
||
Assert.False(status.IsRunning); | ||
Assert.False(status.IsFinished); | ||
Assert.True(status.IsExitedWithError); | ||
Assert.Same(ex, status.Exception); | ||
} | ||
} | ||
} |