Skip to content
Browse files

Refactored to task based jobs. Still not taking full advantage of tas…

…ks, but this sets the stage for that later.
  • Loading branch information...
1 parent 5d199a7 commit b566f4facadcad35c9c821e9432a803b02bb548f @Haacked Haacked committed Oct 14, 2011
View
6 README.md
@@ -32,4 +32,8 @@ tasks to run without requiring a bunch of setup or a connection to Azure.
This is not a general purpose scheduling framework. There are much better ones
out there such as FluentScheduler and Quartz.net. The goal of this project is
to handle one task only, manage a recurring task on an interval in the
-background for a web app.
+background for a web app.
+
+The needs I have are very simple. I didn't need a high fidelity scheduler.
+Maybe later, I'll look to integrate what I've done with one of the others.
+But for now, this scratches an itch.
View
5 src/WebBackgrounder.DemoWeb/SampleJob.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading;
+using System.Threading.Tasks;
namespace WebBackgrounder.DemoWeb
{
@@ -9,9 +10,9 @@ public SampleJob(TimeSpan interval) : base("Sample Job", interval)
{
}
- public override void Execute()
+ public override Task Execute()
{
- Thread.Sleep(3000);
+ return new Task(() => Thread.Sleep(3000));
}
}
}
View
2 src/WebBackgrounder.DemoWeb/WebBackgrounder.DemoWeb.csproj
@@ -146,7 +146,7 @@
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
- <DevelopmentServerPort>56071</DevelopmentServerPort>
+ <DevelopmentServerPort>63168</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
View
28 src/WebBackgrounder.EntityFramework/WorkItemCleanupJob.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using WebBackgrounder.EntityFramework.Entities;
namespace WebBackgrounder.EntityFramework
@@ -28,24 +29,27 @@ public int MaxWorkItemCount
private set;
}
- public override void Execute()
+ public override Task Execute()
{
- var count = _context.WorkItems.Count();
- if (count > MaxWorkItemCount)
+ return new Task(() =>
{
- var oldest = (from workItem in _context.WorkItems
- orderby workItem.Started descending
- select workItem).Skip(MaxWorkItemCount).ToList();
-
- if (oldest.Count > 0)
+ var count = _context.WorkItems.Count();
+ if (count > MaxWorkItemCount)
{
- foreach (var workItem in oldest)
+ var oldest = (from workItem in _context.WorkItems
+ orderby workItem.Started descending
+ select workItem).Skip(MaxWorkItemCount).ToList();
+
+ if (oldest.Count > 0)
{
- _context.WorkItems.Remove(workItem);
+ foreach (var workItem in oldest)
+ {
+ _context.WorkItems.Remove(workItem);
+ }
+ _context.SaveChanges();
}
- _context.SaveChanges();
}
- }
+ });
}
}
}
View
15 src/WebBackgrounder.UnitTests/JobHostFacts.cs
@@ -13,25 +13,32 @@ public class TheStopMethod
public void EnsuresNoWorkIsDone()
{
var host = new JobHost();
- Action work = () => { throw new InvalidOperationException("Hey, this is supposed to be shut down!"); };
+ var task = new Task(() => { throw new InvalidOperationException("Hey, this is supposed to be shut down!"); });
host.Stop(true);
- host.DoWork(work);
+ host.DoWork(task);
}
[Fact]
public void WaitsForTaskToComplete()
{
var host = new JobHost();
- var workTask = new Task(() => host.DoWork(() => Thread.Sleep(100)));
+ var workTask = new Task(() => host.DoWork(new Task(() =>
+ {
+ // Was getting inconsistent results with Thread.Sleep(100)
+ for (int i = 0; i < 100; i++)
+ {
+ Thread.Sleep(1);
+ }
+ })));
var beforeStop = DateTime.UtcNow;
workTask.Start();
while (workTask.Status != TaskStatus.Running)
{
Thread.Sleep(1);
}
-
+
host.Stop(false);
var afterStop = DateTime.UtcNow;
View
4 src/WebBackgrounder.UnitTests/ScheduleFacts.cs
@@ -41,9 +41,11 @@ public void ReturnsTheSpanBetweenNowAndNextRunTime()
[Fact]
public void ReturnsTheSpanBetweenNowAndNextRunTimeFiguringInLastRun()
{
+ var now = DateTime.UtcNow;
+
var job = new Mock<IJob>();
job.Setup(j => j.Interval).Returns(TimeSpan.FromSeconds(30));
- var schedule = new Schedule(job.Object) { LastRunTime = DateTime.UtcNow.AddSeconds(-20)};
+ var schedule = new Schedule(job.Object, () => now) { LastRunTime = now.AddSeconds(-20)};
var interval = schedule.GetIntervalToNextRun();
View
5 src/WebBackgrounder.UnitTests/SchedulerFacts.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Moq;
using Xunit;
@@ -103,8 +105,9 @@ public WaitJob(int intervalSeconds) : base("Waits", TimeSpan.FromSeconds(interva
public int Id { get; private set; }
- public override void Execute()
+ public override Task Execute()
{
+ return new Task(() => Thread.Sleep(1));
}
}
}
View
5 src/WebBackgrounder.UnitTests/WorkItemCleanupJobFacts.cs
@@ -24,8 +24,9 @@ public void DeletesItemsBeyondMaxCount()
new WorkItem {Id = 104, Started = DateTime.UtcNow},
};
var job = new WorkItemCleanupJob(2, TimeSpan.FromSeconds(1), context.Object);
-
- job.Execute();
+ var task = job.Execute();
+ task.Start();
+ task.Wait();
Assert.Equal(2, context.Object.WorkItems.Count());
Assert.Equal(101, context.Object.WorkItems.First().Id);
View
7 src/WebBackgrounder/IJob.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
namespace WebBackgrounder
{
@@ -9,11 +10,7 @@ public interface IJob
/// </summary>
string Name { get; }
- /// <summary>
- /// Executes the task
- /// </summary>
- /// <returns></returns>
- void Execute();
+ Task Execute();
/// <summary>
/// Interval in milliseconds.
View
8 src/WebBackgrounder/IJobCoordinator.cs
@@ -1,11 +1,13 @@
-namespace WebBackgrounder
+using System.Threading.Tasks;
+
+namespace WebBackgrounder
{
public interface IJobCoordinator
{
/// <summary>
- /// Coordinates the work to be done and then does the work if necessary.
+ /// Coordinates the work to be done and returns a task embodying that work.
/// </summary>
/// <param name="job"></param>
- void PerformWork(IJob job);
+ Task PerformWork(IJob job);
}
}
View
3 src/WebBackgrounder/IJobHost.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
namespace WebBackgrounder
{
@@ -8,6 +9,6 @@ namespace WebBackgrounder
/// </summary>
public interface IJobHost
{
- void DoWork(Action work);
+ void DoWork(Task work);
}
}
View
3 src/WebBackgrounder/Job.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
namespace WebBackgrounder
{
@@ -22,7 +23,7 @@ public string Name
private set;
}
- public abstract void Execute();
+ public abstract Task Execute();
public TimeSpan Interval
{
View
17 src/WebBackgrounder/JobHost.cs
@@ -1,12 +1,12 @@
-using System;
+using System.Threading.Tasks;
using System.Web.Hosting;
namespace WebBackgrounder
{
public class JobHost : IJobHost, IRegisteredObject
{
- private readonly object _lock = new object();
- private bool _shuttingDown;
+ readonly object _lock = new object();
+ bool _shuttingDown;
public JobHost()
{
@@ -19,17 +19,24 @@ public void Stop(bool immediate)
{
_shuttingDown = true;
}
+ HostingEnvironment.UnregisterObject(this);
}
- public void DoWork(Action work)
+ public void DoWork(Task work)
{
lock (_lock)
{
if (_shuttingDown)
{
return;
}
- work();
+ work.Start();
+ // Need to hold the lock until the task completes.
+ // Later on, we should take advantage of the fact that the work is represented
+ // by a task. Instead of locking, we could simply have the Stop method cancel
+ // any pending tasks.
+ work.Wait();
+
}
}
}
View
2 src/WebBackgrounder/JobManager.cs
@@ -71,7 +71,7 @@ void PerformTask()
{
using (var schedule = _scheduler.Next())
{
- _host.DoWork(() => _coordinator.PerformWork(schedule.Job));
+ _host.DoWork(_coordinator.PerformWork(schedule.Job));
}
}
View
8 src/WebBackgrounder/SingleServerJobCoordinator.cs
@@ -1,10 +1,12 @@
- namespace WebBackgrounder
+ using System.Threading.Tasks;
+
+namespace WebBackgrounder
{
public class SingleServerJobCoordinator : IJobCoordinator
{
- public void PerformWork(IJob job)
+ public Task PerformWork(IJob job)
{
- job.Execute();
+ return job.Execute();
}
}
}
View
30 src/WebBackgrounder/WebFarmJobCoordinator.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
namespace WebBackgrounder
{
@@ -15,32 +16,37 @@ public WebFarmJobCoordinator(Func<string, IWorkItemRepository> repositoryThunk)
_repositoryThunk = repositoryThunk;
}
- public void PerformWork(IJob jobWorker)
+ public Task PerformWork(IJob job)
{
// We need a new instance every time we perform work.
- var repository = _repositoryThunk(jobWorker.Name);
+ var repository = _repositoryThunk(job.Name);
var unitOfWork = ReserveWork(repository, WorkerId);
if (unitOfWork == null)
{
- return;
+ return null;
}
- try
- {
- jobWorker.Execute();
- unitOfWork.Complete();
- }
- catch (Exception exception)
+ var task = job.Execute();
+ task.ContinueWith(c =>
{
- unitOfWork.Fail(exception);
- }
+ if (c.IsFaulted)
+ {
+ unitOfWork.Fail(c.Exception.GetBaseException());
+ }
+ else
+ {
+ unitOfWork.Complete();
+ }
+
+ });
+ return task;
}
public JobUnitOfWork ReserveWork(IWorkItemRepository repository, string workerId)
{
long? workItemId = null;
-
+
// We do a double check here because this is the first query we run and
// a database can't be created inside a transaction scope.
if (repository.AnyActiveWorker)
View
5 src/WebBackgrounderSolution.sln
@@ -9,6 +9,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.UnitTests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.EntityFramework", "WebBackgrounder.EntityFramework\WebBackgrounder.EntityFramework.csproj", "{06D8DE5D-F101-4CD5-B406-8A211216FCE1}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{3F870BEB-5F40-4CC3-9226-2FBE7A930E81}"
+ ProjectSection(SolutionItems) = preProject
+ ..\README.md = ..\README.md
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

0 comments on commit b566f4f

Please sign in to comment.
Something went wrong with that request. Please try again.