-
Notifications
You must be signed in to change notification settings - Fork 5k
/
SerialTaskQueue.cs
69 lines (60 loc) · 3.18 KB
/
SerialTaskQueue.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
using System.Collections.Generic;
namespace System.Threading.Tasks
{
/// <summary>Represents a queue of tasks to be started and executed serially.</summary>
public class SerialTaskQueue
{
/// <summary>The ordered queue of tasks to be executed. Also serves as a lock protecting all shared state.</summary>
private readonly Queue<object> _tasks = new Queue<object>();
/// <summary>The task currently executing, or null if there is none.</summary>
private Task _taskInFlight;
/// <summary>Enqueues the task to be processed serially and in order.</summary>
/// <param name="taskGenerator">The function that generates a non-started task.</param>
public void Enqueue(Func<Task> taskGenerator) => EnqueueInternal(taskGenerator);
/// <summary>Enqueues the non-started task to be processed serially and in order.</summary>
/// <param name="task">The task.</param>
public Task Enqueue(Task task) { EnqueueInternal(task); return task; }
/// <summary>Gets a Task that represents the completion of all previously queued tasks.</summary>
public Task Completed() => Enqueue(new Task(() => { }));
/// <summary>Enqueues the task to be processed serially and in order.</summary>
/// <param name="taskOrFunction">The task or functino that generates a task.</param>
/// <remarks>The task must not be started and must only be started by this instance.</remarks>
private void EnqueueInternal(object taskOrFunction)
{
// Validate the task
if (taskOrFunction == null) throw new ArgumentNullException(nameof(taskOrFunction));
lock (_tasks)
{
// If there is currently no task in flight, we'll start this one
if (_taskInFlight == null) StartTask_CallUnderLock(taskOrFunction);
// Otherwise, just queue the task to be started later
else _tasks.Enqueue(taskOrFunction);
}
}
/// <summary>Called when a Task completes to potentially start the next in the queue.</summary>
/// <param name="ignored">The task that completed.</param>
private void OnTaskCompletion(Task ignored)
{
lock (_tasks)
{
// The task completed, so nothing is currently in flight.
// If there are any tasks in the queue, start the next one.
_taskInFlight = null;
if (_tasks.Count > 0) StartTask_CallUnderLock(_tasks.Dequeue());
}
}
/// <summary>Starts the provided task (or function that returns a task).</summary>
/// <param name="nextItem">The next task or function that returns a task.</param>
private void StartTask_CallUnderLock(object nextItem)
{
if (!(nextItem is Task next)) next = ((Func<Task>)nextItem)();
if (next.Status == TaskStatus.Created) next.Start();
_taskInFlight = next;
next.ContinueWith(OnTaskCompletion);
}
}
}