Skip to content

Commit

Permalink
Add background task support
Browse files Browse the repository at this point in the history
Allows a background task to be run and monitored, including if it exits
due to an exception
  • Loading branch information
blowfishpro committed Oct 5, 2017
1 parent 78d4635 commit 172e2f3
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 0 deletions.
4 changes: 4 additions & 0 deletions ModuleManager/ModuleManager.csproj
Expand Up @@ -62,8 +62,12 @@
<Compile Include="PatchProgress.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs" />
<Compile Include="Threading\ITaskStatus.cs" />
<Compile Include="Threading\TaskStatus.cs" />
<Compile Include="Threading\TaskStatusWrapper.cs" />
<Compile Include="Utils\FileUtils.cs" />
<Compile Include="CustomConfigsManager.cs" />
<Compile Include="Threading\BackgroundTask.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
Expand Down
33 changes: 33 additions & 0 deletions 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);
}
}
}
12 changes: 12 additions & 0 deletions 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; }
}
}
55 changes: 55 additions & 0 deletions 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;
}
}
}
}
19 changes: 19 additions & 0 deletions 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;
}
}
2 changes: 2 additions & 0 deletions ModuleManagerTests/ModuleManagerTests.csproj
Expand Up @@ -67,6 +67,8 @@
<Compile Include="PatchExtractorTest.cs" />
<Compile Include="PatchProgressTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Threading\TaskStatusTest.cs" />
<Compile Include="Threading\BackgroundTaskTest.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
72 changes: 72 additions & 0 deletions 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<ArgumentNullException>(delegate
{
BackgroundTask.Start(null);
});
}
}
}
121 changes: 121 additions & 0 deletions 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<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);
}
}
}

0 comments on commit 172e2f3

Please sign in to comment.