[f-spot/mipmapped-loading: 2/3] Add start of task parallel infrastructure to overcome lack of C# 4.0.



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]