[f-spot/mipmapped-loading: 2/3] Add start of task parallel infrastructure to overcome lack of C# 4.0.
- From: Ruben Vermeersch <rubenv src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [f-spot/mipmapped-loading: 2/3] Add start of task parallel infrastructure to overcome lack of C# 4.0.
- Date: Sat, 19 Jun 2010 23:47:58 +0000 (UTC)
commit bf1f02cfcc601862f75053becb4b08f37461a007
Author: Ruben Vermeersch <ruben savanne be>
Date: Sun Jun 20 01:21:39 2010 +0200
Add start of task parallel infrastructure to overcome lack of C# 4.0.
src/Makefile.am | 3 +
src/Tasks/QueuedTask.cs | 110 ++++++++++++++++++++++++++
src/Tasks/Task.cs | 178 ++++++++++++++++++++++++++++++++++++++++++
src/Tasks/Tests/TaskTests.cs | 116 +++++++++++++++++++++++++++
4 files changed, 407 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 52d15b0..724096b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -142,6 +142,9 @@ SOURCES = \
TagQueryWidget.cs \
TagSelectionWidget.cs \
TagStore.cs \
+ Tasks/Task.cs \
+ Tasks/QueuedTask.cs \
+ Tasks/Tests/TaskTests.cs \
ThumbnailCache.cs \
ThumbnailGenerator.cs \
Term.cs \
diff --git a/src/Tasks/QueuedTask.cs b/src/Tasks/QueuedTask.cs
new file mode 100644
index 0000000..056f062
--- /dev/null
+++ b/src/Tasks/QueuedTask.cs
@@ -0,0 +1,110 @@
+using Hyena;
+using System;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace FSpot.Tasks
+{
+ class QueueTaskScheduler {
+ static object sync_root = new object ();
+
+ EventWaitHandle wait = new AutoResetEvent (false);
+
+ static QueueTaskScheduler instance;
+ internal static QueueTaskScheduler Instance {
+ get {
+ lock (sync_root) {
+ if (instance == null) {
+ instance = new QueueTaskScheduler ();
+ }
+ }
+ return instance;
+ }
+ set {
+ if (value != null)
+ throw new Exception ("Can only set to null to reset");
+ if (instance != null)
+ instance.Finish ();
+ instance = null;
+ }
+ }
+
+ List<Task> queue = new List<Task> ();
+ Thread worker;
+ volatile bool should_halt = false;
+
+ public QueueTaskScheduler () {
+ worker = ThreadAssist.Spawn (SchedulerWorker);
+ }
+
+ public void Finish ()
+ {
+ should_halt = true;
+ wait.Set ();
+ while (!worker.Join (100))
+ wait.Set ();
+ }
+
+ internal void Schedule (Task task)
+ {
+ lock (queue) {
+ queue.Add (task);
+ wait.Set ();
+ }
+ }
+
+ internal void Unschedule (Task task)
+ {
+ lock (queue) {
+ queue.Remove (task);
+ }
+ }
+
+ void SchedulerWorker ()
+ {
+ while (!should_halt) {
+ Task task = null;
+ lock (queue) {
+ if (queue.Count > 0)
+ task = queue [0];
+ }
+
+ if (task == null && !should_halt) {
+ wait.WaitOne ();
+ continue;
+ }
+
+ lock (queue) {
+ queue.RemoveAt (0);
+ }
+
+ task.Execute ();
+ }
+ }
+ }
+
+ public class QueuedTask<T> : Task<T>
+ {
+ public delegate T TaskHandler ();
+
+ TaskHandler handler;
+
+ public QueuedTask (TaskHandler h) {
+ this.handler = h;
+ }
+
+ protected override void InnerSchedule ()
+ {
+ QueueTaskScheduler.Instance.Schedule (this);
+ }
+
+ protected override void InnerUnschedule ()
+ {
+ QueueTaskScheduler.Instance.Unschedule (this);
+ }
+
+ protected override T InnerExecute () {
+ return handler ();
+ }
+ }
+}
diff --git a/src/Tasks/Task.cs b/src/Tasks/Task.cs
new file mode 100644
index 0000000..428f022
--- /dev/null
+++ b/src/Tasks/Task.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace FSpot.Tasks
+{
+ public interface Task {
+ void Start ();
+ void Cancel ();
+ void Execute ();
+ }
+
+ interface IChildrenHandling
+ {
+ void RemoveChild (Task task);
+ }
+
+ interface ISchedulable
+ {
+ void Schedule ();
+ void Unschedule ();
+ }
+
+ public enum TaskState
+ {
+ Pending,
+ Scheduled,
+ Completed,
+ Cancelled,
+ Exception
+ }
+
+ public abstract class Task<T> : Task, ISchedulable, IChildrenHandling
+ {
+ public bool CancelWithChildren { get; set; }
+
+ private Task Parent { get; set; }
+ private List<Task> Children { get; set; }
+
+ private T result;
+ public T Result {
+ get {
+ Wait ();
+ return result;
+ }
+ }
+
+ private volatile TaskState state;
+ public TaskState State {
+ get { return state; }
+ private set {
+ //Hyena.Log.DebugFormat ("State Change: {0} : {1} => {2}", this, state, value);
+ state = value;
+ }
+ }
+
+ private EventWaitHandle WaitEvent { get; set; }
+
+ public Task ()
+ {
+ CancelWithChildren = false;
+ Children = new List<Task> ();
+ WaitEvent = new ManualResetEvent (false);
+ State = TaskState.Pending;
+ }
+
+ public void Start ()
+ {
+ if (Parent != null)
+ throw new Exception ("You should not start child tasks yourself");
+ (this as ISchedulable).Schedule ();
+ }
+
+ public void Wait ()
+ {
+ if (Parent == null)
+ (this as ISchedulable).Schedule ();
+ WaitEvent.WaitOne ();
+ }
+
+ public void ContinueWith (Task<T> task)
+ {
+ ContinueWith (task, true);
+ }
+
+ public void ContinueWith (Task<T> task, bool autostart)
+ {
+ lock (Children) {
+ if (State == TaskState.Completed) {
+ task.Start ();
+ } else {
+ task.Parent = this;
+ Children.Add (task);
+ if (autostart) {
+ var to_start = Parent ?? this;
+ to_start.Start ();
+ }
+ }
+ }
+ }
+
+ void IChildrenHandling.RemoveChild (Task task)
+ {
+ lock (Children) {
+ Children.Remove (task);
+ if (Children.Count == 0 && CancelWithChildren)
+ Cancel ();
+ }
+ }
+
+ public void Cancel ()
+ {
+ State = TaskState.Cancelled;
+ (this as ISchedulable).Unschedule ();
+
+ if (Parent != null)
+ (Parent as IChildrenHandling).RemoveChild (this);
+
+ lock (Children) {
+ foreach (var child in Children) {
+ child.Cancel ();
+ }
+ }
+
+ WaitEvent.Set ();
+ }
+
+ public void Execute ()
+ {
+ if (State != TaskState.Scheduled && State != TaskState.Cancelled)
+ throw new Exception ("Can't start task manually!");
+
+ if (State == TaskState.Cancelled)
+ return;
+
+ try {
+ result = InnerExecute ();
+ State = TaskState.Completed;
+
+ foreach (var child in Children) {
+ (child as ISchedulable).Schedule ();
+ }
+ } catch (Exception e) {
+ State = TaskState.Exception;
+ throw e;
+ } finally {
+ WaitEvent.Set ();
+ }
+ }
+
+ protected abstract T InnerExecute ();
+
+#region Scheduling
+
+ void ISchedulable.Schedule ()
+ {
+ if (State == TaskState.Completed || State == TaskState.Scheduled)
+ return;
+ if (State != TaskState.Pending)
+ throw new Exception ("Can only schedule pending tasks!");
+ State = TaskState.Scheduled;
+ InnerSchedule ();
+ }
+
+ void ISchedulable.Unschedule ()
+ {
+ if (State == TaskState.Scheduled)
+ State = TaskState.Pending;
+ InnerUnschedule ();
+ }
+
+ protected abstract void InnerSchedule ();
+ protected abstract void InnerUnschedule ();
+
+#endregion
+
+ }
+}
diff --git a/src/Tasks/Tests/TaskTests.cs b/src/Tasks/Tests/TaskTests.cs
new file mode 100644
index 0000000..7ab05a8
--- /dev/null
+++ b/src/Tasks/Tests/TaskTests.cs
@@ -0,0 +1,116 @@
+#if ENABLE_TESTS
+using NUnit.Framework;
+using System;
+using System.Threading;
+using Hyena;
+using FSpot;
+
+namespace FSpot.Tasks.Tests
+{
+ [TestFixture]
+ public class TaskTests
+ {
+ [SetUp]
+ public void Initialize () {
+ QueueTaskScheduler.Instance = null;
+ Hyena.Log.Debugging = true;
+ }
+
+ [Test]
+ public void TestWait () {
+ var t = new SimpleTask<bool> (() => {
+ Thread.Sleep (100);
+ return true;
+ });
+ var start = DateTime.Now.Ticks;
+
+ t.Start ();
+ Assert.Less ((DateTime.Now.Ticks - start) / 10000, 101);
+ Assert.AreEqual (TaskState.Scheduled, t.State);
+
+ t.Wait ();
+ Assert.Greater ((DateTime.Now.Ticks - start) / 10000, 99);
+ Assert.AreEqual (TaskState.Completed, t.State);
+ }
+
+ [Test]
+ public void TestContinue () {
+ var t = new SimpleTask<bool> (() => {
+ return true;
+ });
+ // Task is initially pending
+ Assert.AreEqual (TaskState.Pending, t.State);
+
+ var t2 = new SimpleTask<bool> (() => {
+ // Parent task is completed before child starts
+ Assert.AreEqual (TaskState.Completed, t.State);
+ return true;
+ });
+ t.ContinueWith (t2);
+ t2.Wait ();
+ }
+
+ [Test]
+ public void TestContinueAfterComplete () {
+ var t = new SimpleTask<bool> (() => {
+ return true;
+ });
+ // Task is initially pending
+ Assert.AreEqual (TaskState.Pending, t.State);
+
+ // Make sure 't' is completed before adding continuation.
+ t.Start ();
+ t.Wait ();
+ Assert.AreEqual (TaskState.Completed, t.State);
+
+ var t2 = new SimpleTask<bool> (() => {
+ // Parent task is completed before child starts
+ Assert.AreEqual (TaskState.Completed, t.State);
+ return true;
+ });
+ t.ContinueWith (t2);
+ t2.Wait ();
+ Assert.AreEqual (TaskState.Completed, t2.State);
+ }
+
+ [Test]
+ public void TestContinueAfterCancel () {
+ var t = new SimpleTask<bool> (() => {
+ Thread.Sleep (100);
+ return true;
+ }, "t");
+ // Task is initially pending
+ Assert.AreEqual (TaskState.Pending, t.State);
+
+ var t2 = new SimpleTask<bool> (() => {
+ // Since t hasn't completed when t2 is cancelled, this should
+ // not be called.
+ throw new Exception ("Should not be ran after cancel!");
+ }, "t2");
+ t.ContinueWith (t2);
+ t2.Cancel ();
+ t2.Wait ();
+ t.Wait ();
+ // t completes because it was started.
+ Assert.AreEqual (TaskState.Completed, t.State);
+ Assert.AreEqual (TaskState.Cancelled, t2.State);
+ }
+
+ class SimpleTask<T> : QueuedTask<T> {
+ string label = null;
+
+ public SimpleTask (TaskHandler h) : base (h) {
+ }
+
+ public SimpleTask (TaskHandler h, string label) : base (h) {
+ this.label = label;
+ }
+
+ public override string ToString ()
+ {
+ return label ?? base.ToString ();
+ }
+ }
+ }
+}
+#endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]