Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/Microsoft.VisualStudio.Threading/AsyncQueue`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,15 @@ public class AsyncQueue<T> : ThreadingTools.ICancellationNotification
/// </remarks>
private volatile TaskCompletionSource<object?>? completedSource;

/// <summary>
/// The factory for the internal queue of elements. A Queue`1 is constructed, when this is null.
/// </summary>
private readonly Func<int, ISyncQueue<T>>? elementsQueueFactory;
Comment on lines +35 to +38

/// <summary>
/// The internal queue of elements. Lazily constructed.
/// </summary>
private Queue<T>? queueElements;
private ISyncQueue<T>? queueElements;

/// <summary>
/// The internal queue of <see cref="DequeueAsync(CancellationToken)"/> waiters. Lazily constructed.
Expand All @@ -59,6 +64,15 @@ public AsyncQueue()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AsyncQueue{T}"/> class.
/// </summary>
/// <param name="elementsQueueFactory">Factory to create the queue used for items, if needed. Takes the initial capacity.</param>
public AsyncQueue(Func<int, ISyncQueue<T>> elementsQueueFactory)
{
this.elementsQueueFactory = elementsQueueFactory;
}
Comment on lines +70 to +74

/// <summary>
/// Gets a value indicating whether the queue is currently empty.
/// </summary>
Expand Down Expand Up @@ -202,7 +216,9 @@ public bool TryEnqueue(T value)
{
if (this.queueElements is null)
{
this.queueElements = new Queue<T>(this.InitialCapacity);
this.queueElements = this.elementsQueueFactory is not null
? this.elementsQueueFactory(this.InitialCapacity)
: new SyncQueue<T>(this.InitialCapacity);
}

this.queueElements.Enqueue(value);
Expand Down
41 changes: 41 additions & 0 deletions src/Microsoft.VisualStudio.Threading/ISyncQueue`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;

namespace Microsoft.VisualStudio.Threading;

/// <summary>
/// A synchronously dequeuable queue. There are no thread-safety guarantees.
/// </summary>
/// <typeparam name="T">The type of values kept by the queue.</typeparam>
public interface ISyncQueue<T>
{
/// <summary>
/// Gets the number of elements currently in the queue.
/// </summary>
int Count { get; }

/// <summary>
/// Adds an element to the tail of the queue.
/// </summary>
/// <param name="value">The value to add.</param>
void Enqueue(T value);
Comment on lines +19 to +23

/// <summary>
/// Gets the value at the head of the queue without removing it from the queue.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the queue is empty.</exception>
T Peek();

/// <summary>
/// Gets and removes the element at the head of the queue.
/// </summary>
/// <returns>The head element.</returns>
T Dequeue();
Comment on lines +31 to +35

/// <summary>
/// Returns a copy of this queue as an array.
/// </summary>
T[] ToArray();
}
28 changes: 28 additions & 0 deletions src/Microsoft.VisualStudio.Threading/SyncQueue`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Diagnostics;

namespace Microsoft.VisualStudio.Threading;

/// <summary>
/// A non-thread-safe wrapper around Queue.
/// </summary>
/// <typeparam name="T">The type of values kept by the queue.</typeparam>
[DebuggerDisplay("Count = {Count}")]
internal sealed class SyncQueue<T>(int capacity) : ISyncQueue<T>
{
private readonly Queue<T> queue = new(capacity);

public int Count => this.queue.Count;

public void Enqueue(T value) => this.queue.Enqueue(value);

public T Peek() => this.queue.Peek();

public T Dequeue() => this.queue.Dequeue();

public T[] ToArray() => this.queue.ToArray();
}

56 changes: 56 additions & 0 deletions test/Microsoft.VisualStudio.Threading.Tests/AsyncQueueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -576,6 +577,24 @@ public void DequeueAsyncContinuationsNotInlinedWithinPrivateLock()
continuationTask.Wait(UnexpectedTimeout);
}

[Fact]
public async Task AsyncQueueCanUseACustomQueueForElements()
{
var asyncQueue = new AsyncQueue<int>(capacity => new PrioritizedQueue(capacity));

asyncQueue.Enqueue(3);
asyncQueue.Enqueue(1);
asyncQueue.Enqueue(2);
asyncQueue.Complete();

Assert.Equal(1, asyncQueue.Peek());
Assert.Equal(1, await asyncQueue.DequeueAsync());
Assert.Equal(2, await asyncQueue.DequeueAsync());
Assert.True(asyncQueue.TryDequeue(out int item));
Assert.Equal(3, item);
Assert.False(asyncQueue.TryDequeue(out item));
}

private class DerivedQueue<T> : AsyncQueue<T>
{
internal Action<T, bool>? OnEnqueuedDelegate { get; set; }
Expand Down Expand Up @@ -605,4 +624,41 @@ protected override void OnCompleted()
this.OnCompletedDelegate?.Invoke();
}
}

/// <summary>
/// This "prioritied" queue implementation is not optimized for speed, nor does it allow custom types or ordering.
/// </summary>
Comment on lines +628 to +630
private class PrioritizedQueue(int capacity) : ISyncQueue<int>
{
private readonly List<int> items = new List<int>(capacity);

public int Count => this.items.Count;

public void Enqueue(int value)
{
int index = this.items.BinarySearch(value);

// for more interesting types, there would be an `else`, moving to the end of items with the same priority, but you can't tell `int`s apart anyway.
if (index < 0)
{
index = ~index;
}

this.items.Insert(index, value);
}

public int Peek() => this.items[0];

public int Dequeue()
{
var item = this.items[0];
this.items.RemoveAt(0);
return item;
}

public int[] ToArray()
{
return this.items.ToArray();
}
}
}