/// <summary>
        /// Enqueue a master job. A master job requires exclusive access to all chunks.
        /// </summary>
        /// <param name="work">The work to be executed by the job.</param>
        /// <param name="reserveQueue">Evaluates whether the job can be enqueued. If so, the master queue is reserved
        /// to indicate that this job is currently queued or executing.</param>
        /// <param name="unreserveQueue">Un-reserve the master queue to indicate that this job is no longer queued or
        /// executing.</param>
        /// <param name="canSkip">Indicates whether the job can be skipped.</param>
        /// <returns>True if the job was enqueued.</returns>
        public bool EnqueueMaster(
            Action work,
            Predicate<MasterJobQueue> reserveQueue,
            Action<MasterJobQueue> unreserveQueue,
            bool canSkip)
        {
            // Create the job
            var job = new MasterJob(work, unreserveQueue, canSkip, this.chunkQueues.Count + 10);
            job.IsPendingChanged += this.Job_IsPendingChanged;
            job.Completed += this.Job_Completed;

            // Enqueue the job if it can be reserved
            this.queuesLock.Enter();
            try
            {
                // Reserve the master queue
                if (!reserveQueue(this.masterQueue))
                {
                    return false;
                }

                // Build the set of owners
                int i = 0;
                ChunkJobQueue[] chunkQueues = new ChunkJobQueue[this.chunkQueues.Count];
                foreach (ChunkJobQueue queue in this.chunkQueues.Values)
                {
                    chunkQueues[i++] = queue;
                }

                // Add the owners and enqueue the job
                job.AddOwners(this.masterQueue);
                job.AddOwners(chunkQueues);
                this.masterQueue.Enqueue(job);
                foreach (ChunkJobQueue queue in chunkQueues)
                {
                    queue.Enqueue(job);
                }

                // Retain a reference to this master job
                this.masterQueueJobs.Add(job);
            }
            finally
            {
                this.queuesLock.Exit();
            }

            return true;
        }
 /// <summary>
 /// Initialise the job queue for the given chunk.
 /// </summary>
 /// <param name="chunk">The chunk.</param>
 /// <returns>The job queue.</returns>
 private ChunkJobQueue InitialiseQueue(Vector2I chunk)
 {
     var queue = new ChunkJobQueue(chunk, this.masterQueueJobs);
     queue.Idle += this.ChunkJobs_QueueIdle;
     this.chunkQueues.Add(chunk, queue);
     return queue;
 }
        /// <summary>
        /// Enqueue a job which requires exclusive access to one or more chunks. This must be called between
        /// BeginEnqueueChunks and EndEnqueueChunks.
        /// </summary>
        /// <param name="work">The work to be executed by the job.</param>
        /// <param name="reserveQueue">Reserve each queue to indicate that this job is currently queued or executing.
        /// </param>
        /// <param name="unreserveQueue">Un-reserve each queue to indicate that this job is no longer queued or
        /// executing.</param>
        /// <param name="canSkip">Indicates whether the job can be skipped.</param>
        /// <param name="chunks">The chunks to which this job requires exclusive access.</param>
        public void EnqueueChunks(
            Action work,
            Action<ChunkJobQueue> reserveQueue,
            Action<ChunkJobQueue> unreserveQueue,
            bool canSkip,
            Vector2I[] chunks)
        {
            // Create the job
            var job = new ChunkJob(work, unreserveQueue, canSkip, chunks.Length);
            job.IsPendingChanged += this.Job_IsPendingChanged;
            job.Completed += this.Job_Completed;

            // Build the set of owners, checking whether the job can be enqueued
            ChunkJobQueue[] chunkQueues = new ChunkJobQueue[chunks.Length];
            for (int i = 0; i < chunks.Length; i++)
            {
                chunkQueues[i] = this.GetOrInitialiseQueue(chunks[i]);
            }

            // Reserve the owner queues
            foreach (ChunkJobQueue queue in chunkQueues)
            {
                reserveQueue(queue);
            }

            // Add the owners and enqueue the job
            job.AddOwners(chunkQueues);
            foreach (ChunkJobQueue queue in chunkQueues)
            {
                queue.Enqueue(job);
            }
        }