diff --git a/STPTests/STPTests.csproj b/STPTests/STPTests.csproj index f3ba95b..49c8e50 100644 --- a/STPTests/STPTests.csproj +++ b/STPTests/STPTests.csproj @@ -1,7 +1,7 @@  Local - 9.0.21022 + 9.0.30729 2.0 {6A3E4DBF-12AD-4636-ACB3-24B5172FAE03} Debug @@ -91,7 +91,7 @@ prompt - + System @@ -189,6 +189,7 @@ Code + diff --git a/STPTests/TestCancel.cs b/STPTests/TestCancel.cs index bd2a13d..f255138 100644 --- a/STPTests/TestCancel.cs +++ b/STPTests/TestCancel.cs @@ -155,6 +155,7 @@ namespace SmartThreadPoolTests stp.Shutdown(); } + /// /// 1. Create STP in suspended mode /// 2. Queue work item into the STP @@ -173,7 +174,7 @@ namespace SmartThreadPoolTests stpStartInfo.StartSuspended = true; SmartThreadPool stp = new SmartThreadPool(stpStartInfo); - IWorkItemResult wir = stp.QueueWorkItem(state => { return null; }); + IWorkItemResult wir = stp.QueueWorkItem(state => null); int counter = 0; diff --git a/STPTests/TestWorkItemTimeout.cs b/STPTests/TestWorkItemTimeout.cs new file mode 100644 index 0000000..9fd1420 --- /dev/null +++ b/STPTests/TestWorkItemTimeout.cs @@ -0,0 +1,126 @@ +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + [TestFixture] + [Category("TestWorkItemTimeout")] + public class TestWorkItemTimeout + { + /// + /// 1. Create STP in suspended mode + /// 2. Queue work item into the STP + /// 3. Wait for the work item to expire + /// 4. Work item's GetResult should throw WorkItemCancelException + /// + [Test] + [ExpectedException(typeof(WorkItemCancelException))] + public void CancelInQueueWorkItem() + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.StartSuspended = true; + + bool hasRun = false; + + SmartThreadPool stp = new SmartThreadPool(stpStartInfo); + IWorkItemResult wir = stp.QueueWorkItem( + new WorkItemInfo() + { + Timeout = 1000 }, + arg => + { + hasRun = true; + return null; + } + ); + + Assert.IsFalse(wir.IsCanceled); + + Thread.Sleep(2000); + + Assert.IsTrue(wir.IsCanceled); + + stp.Start(); + stp.WaitForIdle(); + + Assert.IsFalse(hasRun); + + try + { + wir.GetResult(); + } + finally + { + stp.Shutdown(); + } + } + + /// + /// 1. Create STP + /// 2. Queue work item that takes some time + /// 3. Wait for it to start + /// 4. The work item timeout expires + /// 5. Make sure, in the work item, that SmartThreadPool.IsWorkItemCanceled is true + /// 5. Wait for the STP to get idle + /// 6. Work item's GetResult should throw WorkItemCancelException + /// + [Test] + public void TimeoutInProgressWorkItemWithSample() + { + bool timedout = false; + ManualResetEvent waitToStart = new ManualResetEvent(false); + ManualResetEvent waitToComplete = new ManualResetEvent(false); + + SmartThreadPool stp = new SmartThreadPool(); + IWorkItemResult wir = stp.QueueWorkItem( + new WorkItemInfo() { Timeout = 1000 }, + state => + { + waitToStart.Set(); + Thread.Sleep(1000); + timedout = SmartThreadPool.IsWorkItemCanceled; + waitToComplete.Set(); + return null; + }); + + waitToStart.WaitOne(); + + waitToComplete.WaitOne(); + + Assert.IsTrue(timedout); + + stp.Shutdown(); + } + + /// + /// 1. Create STP + /// 2. Queue work item into the STP + /// 3. Wait for the STP to get idle + /// 4. Work item's GetResult should return value + /// 4. The work item expires + /// 5. Work item's GetResult should return value + /// + [Test] + public void TimeoutCompletedWorkItem() + { + SmartThreadPool stp = new SmartThreadPool(); + IWorkItemResult wir = + stp.QueueWorkItem( + new WorkItemInfo() { Timeout = 1000 }, + state => 1); + + stp.WaitForIdle(); + + Assert.AreEqual(wir.GetResult(), 1); + + Thread.Sleep(1000); + + Assert.AreEqual(wir.GetResult(), 1); + + stp.Shutdown(); + } + } +} diff --git a/SmartThreadPool/WorkItem.cs b/SmartThreadPool/WorkItem.cs index 0fd60e3..5d0e221 100644 --- a/SmartThreadPool/WorkItem.cs +++ b/SmartThreadPool/WorkItem.cs @@ -61,11 +61,11 @@ namespace Amib.Threading.Internal /// private object _state; - /// - /// Stores the caller's context - /// #if !(WindowsCE) && !(SILVERLIGHT) - private readonly CallerThreadContext _callerContext; + /// + /// Stores the caller's context + /// + private readonly CallerThreadContext _callerContext; #endif /// /// Holds the result of the mehtod @@ -136,6 +136,11 @@ namespace Amib.Threading.Internal /// private Thread _executingThread; + /// + /// The absulote time when the work item will be timeout + /// + private long _expirationTime; + #region Performance Counter fields @@ -227,6 +232,10 @@ namespace Amib.Threading.Internal _workItemCompletedRefCount = 0; _waitingOnQueueStopwatch = new Stopwatch(); _processingStopwatch = new Stopwatch(); + _expirationTime = + _workItemInfo.Timeout > 0 ? + DateTime.UtcNow.Ticks + _workItemInfo.Timeout * TimeSpan.TicksPerMillisecond : + long.MaxValue; } internal bool WasQueuedBy(IWorkItemsGroup workItemsGroup) @@ -625,7 +634,19 @@ namespace Amib.Threading.Internal { lock (this) { - if (WorkItemState.Completed == _workItemState || WorkItemState.InProgress == _workItemState) + if (WorkItemState.Completed == _workItemState) + { + return _workItemState; + } + + long nowTicks = DateTime.UtcNow.Ticks; + + if (WorkItemState.Canceled != _workItemState && nowTicks > _expirationTime) + { + _workItemState = WorkItemState.Canceled; + } + + if (WorkItemState.InProgress == _workItemState) { return _workItemState; } diff --git a/SmartThreadPool/WorkItemFactory.cs b/SmartThreadPool/WorkItemFactory.cs index 3f0dccd..16ccd81 100644 --- a/SmartThreadPool/WorkItemFactory.cs +++ b/SmartThreadPool/WorkItemFactory.cs @@ -332,7 +332,7 @@ namespace Amib.Threading.Internal private static void ValidateCallback(Delegate callback) { - if(callback.GetInvocationList().Length > 1) + if (callback != null && callback.GetInvocationList().Length > 1) { throw new NotSupportedException("SmartThreadPool doesn't support delegates chains"); } diff --git a/SmartThreadPool/WorkItemInfo.cs b/SmartThreadPool/WorkItemInfo.cs index 46799b9..0d7fc85 100644 --- a/SmartThreadPool/WorkItemInfo.cs +++ b/SmartThreadPool/WorkItemInfo.cs @@ -25,6 +25,7 @@ namespace Amib.Threading CallToPostExecute = workItemInfo.CallToPostExecute; PostExecuteWorkItemCallback = workItemInfo.PostExecuteWorkItemCallback; WorkItemPriority = workItemInfo.WorkItemPriority; + Timeout = workItemInfo.Timeout; } /// @@ -53,9 +54,15 @@ namespace Amib.Threading public PostExecuteWorkItemCallback PostExecuteWorkItemCallback { get; set; } /// - /// Get/Set the work items priority + /// Get/Set the work item's priority /// public WorkItemPriority WorkItemPriority { get; set; } + + /// + /// Get/Set the work item's timout in milliseconds. + /// This is a passive timout. When the timout expires the work item won't be actively aborted! + /// + public long Timeout { get; set; } } #endregion