1 // ***********************************************************************
2 // Copyright (c) 2012-2014 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
33 using System.Collections.Generic;
34 using System.Threading;
36 namespace NUnit.Framework.Internal.Execution
39 /// ParallelWorkItemDispatcher handles execution of work items by
40 /// queuing them for worker threads to process.
42 public class ParallelWorkItemDispatcher : IWorkItemDispatcher
44 private static readonly Logger log = InternalTrace.GetLogger("WorkItemDispatcher");
46 private readonly int _levelOfParallelism;
47 private int _itemsDispatched;
50 private readonly WorkShift _parallelShift = new WorkShift("Parallel");
51 private readonly WorkShift _nonParallelShift = new WorkShift("NonParallel");
52 private readonly Lazy<WorkItemQueue> _parallelQueue;
53 private readonly Lazy<WorkItemQueue> _nonParallelQueue;
57 private readonly WorkShift _nonParallelSTAShift = new WorkShift("NonParallelSTA");
58 private readonly Lazy<WorkItemQueue> _parallelSTAQueue;
59 private readonly Lazy<WorkItemQueue> _nonParallelSTAQueue;
62 // The first WorkItem to be dispatched, assumed to be top-level item
63 private WorkItem _topLevelWorkItem;
66 /// Construct a ParallelWorkItemDispatcher
68 /// <param name="levelOfParallelism">Number of workers to use</param>
69 public ParallelWorkItemDispatcher(int levelOfParallelism)
71 _levelOfParallelism = levelOfParallelism;
73 Shifts = new WorkShift[]
81 foreach (var shift in Shifts)
82 shift.EndOfShift += OnEndOfShift;
84 _parallelQueue = new Lazy<WorkItemQueue>(() =>
86 var parallelQueue = new WorkItemQueue("ParallelQueue");
87 _parallelShift.AddQueue(parallelQueue);
89 for (int i = 1; i <= _levelOfParallelism; i++)
91 string name = string.Format("Worker#" + i.ToString());
93 _parallelShift.Assign(new TestWorker(parallelQueue, name));
95 _parallelShift.Assign(new TestWorker(parallelQueue, name, ApartmentState.MTA));
103 _parallelSTAQueue = new Lazy<WorkItemQueue>(() =>
105 var parallelSTAQueue = new WorkItemQueue("ParallelSTAQueue");
106 _parallelShift.AddQueue(parallelSTAQueue);
107 _parallelShift.Assign(new TestWorker(parallelSTAQueue, "Worker#STA", ApartmentState.STA));
109 return parallelSTAQueue;
113 _nonParallelQueue = new Lazy<WorkItemQueue>(() =>
115 var nonParallelQueue = new WorkItemQueue("NonParallelQueue");
116 _nonParallelShift.AddQueue(nonParallelQueue);
118 _nonParallelShift.Assign(new TestWorker(nonParallelQueue, "Worker#NP"));
120 _nonParallelShift.Assign(new TestWorker(nonParallelQueue, "Worker#STA_NP", ApartmentState.MTA));
123 return nonParallelQueue;
127 _nonParallelSTAQueue = new Lazy<WorkItemQueue>(() =>
129 var nonParallelSTAQueue = new WorkItemQueue("NonParallelSTAQueue");
130 _nonParallelSTAShift.AddQueue(nonParallelSTAQueue);
131 _nonParallelSTAShift.Assign(new TestWorker(nonParallelSTAQueue, "Worker#NP_STA", ApartmentState.STA));
133 return nonParallelSTAQueue;
139 /// Enumerates all the shifts supported by the dispatcher
141 public IEnumerable<WorkShift> Shifts { get; private set; }
143 #region IWorkItemDispatcher Members
146 /// Dispatch a single work item for execution. The first
147 /// work item dispatched is saved as the top-level
148 /// work item and used when stopping the run.
150 /// <param name="work">The item to dispatch</param>
151 public void Dispatch(WorkItem work)
153 // Special handling of the top-level item
154 if (Interlocked.CompareExchange (ref _topLevelWorkItem, work, null) == null)
159 // We run child items directly, rather than enqueuing them...
160 // 1. If the context is single threaded.
161 // 2. If there is no fixture, and so nothing to do but dispatch grandchildren.
162 // 3. For now, if this represents a test case. This avoids issues of
163 // tests that access the fixture state and allows handling ApartmentState
164 // preferences set on the fixture.
165 else if (work.Context.IsSingleThreaded
166 || work.Test.TypeInfo == null
167 || work is SimpleWorkItem)
172 Interlocked.Increment(ref _itemsDispatched);
175 private void Execute(WorkItem work)
177 log.Debug("Directly executing {0}", work.Test.Name);
181 private void Enqueue(WorkItem work)
183 log.Debug("Enqueuing {0}", work.Test.Name);
185 if (work.IsParallelizable)
188 if (work.TargetApartment == ApartmentState.STA)
189 ParallelSTAQueue.Enqueue(work);
192 ParallelQueue.Enqueue(work);
195 else if (work.TargetApartment == ApartmentState.STA)
196 NonParallelSTAQueue.Enqueue(work);
199 NonParallelQueue.Enqueue(work);
203 /// Cancel the ongoing run completely.
204 /// If no run is in process, the call has no effect.
206 public void CancelRun(bool force)
208 foreach (var shift in Shifts)
214 #region Private Queue Properties
216 // Queues are not actually created until the first time the property
217 // is referenced by the Dispatch method adding a WorkItem to it.
219 private WorkItemQueue ParallelQueue
223 return _parallelQueue.Value;
228 private WorkItemQueue ParallelSTAQueue
232 return _parallelSTAQueue.Value;
237 private WorkItemQueue NonParallelQueue
241 return _nonParallelQueue.Value;
246 private WorkItemQueue NonParallelSTAQueue
250 return _nonParallelSTAQueue.Value;
256 #region Helper Methods
258 private void OnEndOfShift(object sender, EventArgs ea)
260 if (!StartNextShift())
262 foreach (var shift in Shifts)
267 private bool StartNextShift()
269 foreach (var shift in Shifts)