diff --git a/src/PowerShellEditorServices.Host.x86/PowerShellEditorServices.Host.x86.csproj b/src/PowerShellEditorServices.Host.x86/PowerShellEditorServices.Host.x86.csproj
index e82969ef8..580b738c5 100644
--- a/src/PowerShellEditorServices.Host.x86/PowerShellEditorServices.Host.x86.csproj
+++ b/src/PowerShellEditorServices.Host.x86/PowerShellEditorServices.Host.x86.csproj
@@ -37,18 +37,6 @@
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll
- True
-
@@ -71,7 +59,6 @@
App.config
-
diff --git a/src/PowerShellEditorServices.Host.x86/packages.config b/src/PowerShellEditorServices.Host.x86/packages.config
deleted file mode 100644
index d27aecea3..000000000
--- a/src/PowerShellEditorServices.Host.x86/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj b/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj
index 74c21b635..6032de252 100644
--- a/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj
+++ b/src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj
@@ -39,18 +39,6 @@
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll
- True
-
@@ -69,7 +57,6 @@
PreserveNewest
-
diff --git a/src/PowerShellEditorServices.Host/packages.config b/src/PowerShellEditorServices.Host/packages.config
deleted file mode 100644
index d27aecea3..000000000
--- a/src/PowerShellEditorServices.Host/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/NextRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/NextRequest.cs
index 42dd7ba60..5cb2e6acd 100644
--- a/src/PowerShellEditorServices.Protocol/DebugAdapter/NextRequest.cs
+++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/NextRequest.cs
@@ -4,8 +4,6 @@
//
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
-using Nito.AsyncEx;
-using System.Threading.Tasks;
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
{
diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetExceptionBreakpointsRequest.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetExceptionBreakpointsRequest.cs
index c86d33b09..3e6109fe5 100644
--- a/src/PowerShellEditorServices.Protocol/DebugAdapter/SetExceptionBreakpointsRequest.cs
+++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/SetExceptionBreakpointsRequest.cs
@@ -4,8 +4,6 @@
//
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
-using Nito.AsyncEx;
-using System.Threading.Tasks;
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
{
diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs
index df28bd2a5..e28822653 100644
--- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs
+++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs
@@ -4,7 +4,6 @@
//
using Microsoft.PowerShell.EditorServices.Utility;
-using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Threading;
@@ -26,6 +25,9 @@ public class MessageDispatcher
private Action responseHandler;
+ private CancellationTokenSource messageLoopCancellationToken =
+ new CancellationTokenSource();
+
#endregion
#region Properties
@@ -65,22 +67,24 @@ public MessageDispatcher(
public void Start()
{
+
// Start the main message loop thread. The Task is
// not explicitly awaited because it is running on
// an independent background thread.
- this.messageLoopThread = new AsyncContextThread(true);
+ this.messageLoopThread = new AsyncContextThread("Message Dispatcher");
this.messageLoopThread
- .Factory
- .Run(this.ListenForMessages)
+ .Run(() => this.ListenForMessages(this.messageLoopCancellationToken.Token))
.ContinueWith(this.OnListenTaskCompleted);
}
public void Stop()
{
- // By disposing the thread we cancel all existing work
- // and cause the thread to be destroyed.
+ // Stop the message loop thread
if (this.messageLoopThread != null)
- this.messageLoopThread.Dispose();
+ {
+ this.messageLoopCancellationToken.Cancel();
+ this.messageLoopThread.Stop();
+ }
}
public void SetRequestHandler(
@@ -181,13 +185,13 @@ protected void OnUnhandledException(Exception unhandledException)
#region Private Methods
- private async Task ListenForMessages()
+ private async Task ListenForMessages(CancellationToken cancellationToken)
{
this.SynchronizationContext = SynchronizationContext.Current;
// Run the message loop
bool isRunning = true;
- while (isRunning)
+ while (isRunning && !cancellationToken.IsCancellationRequested)
{
Message newMessage = null;
diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs
index 3e6c0c784..a2c1db19e 100644
--- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs
+++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs
@@ -6,7 +6,6 @@
using Microsoft.PowerShell.EditorServices.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using Nito.AsyncEx;
using System.IO;
using System.Text;
using System.Threading.Tasks;
diff --git a/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj b/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj
index 2a8885546..cab4d9c6a 100644
--- a/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj
+++ b/src/PowerShellEditorServices.Protocol/PowerShellEditorServices.Protocol.csproj
@@ -38,18 +38,6 @@
..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dllTrue
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll
- True
-
diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
index 0ed6be271..030f879dc 100644
--- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
+++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs
@@ -8,7 +8,6 @@
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
using Microsoft.PowerShell.EditorServices.Protocol.Messages;
using Microsoft.PowerShell.EditorServices.Utility;
-using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.IO;
@@ -780,7 +779,7 @@ private Task RunScriptDiagnostics(
if (!this.currentSettings.ScriptAnalysis.Enable.Value)
{
// If the user has disabled script analysis, skip it entirely
- return TaskConstants.Completed;
+ return Task.FromResult(true);
}
// If there's an existing task, attempt to cancel it
@@ -806,7 +805,9 @@ private Task RunScriptDiagnostics(
"Exception while cancelling analysis task:\n\n{0}",
e.ToString()));
- return TaskConstants.Canceled;
+ TaskCompletionSource cancelTask = new TaskCompletionSource();
+ cancelTask.SetCanceled();
+ return cancelTask.Task;
}
// Create a fresh cancellation token and then start the task.
@@ -826,7 +827,7 @@ private Task RunScriptDiagnostics(
TaskCreationOptions.None,
TaskScheduler.Default);
- return TaskConstants.Completed;
+ return Task.FromResult(true);
}
private static async Task DelayThenInvokeDiagnostics(
diff --git a/src/PowerShellEditorServices.Protocol/packages.config b/src/PowerShellEditorServices.Protocol/packages.config
index 5853f7f1e..505e58836 100644
--- a/src/PowerShellEditorServices.Protocol/packages.config
+++ b/src/PowerShellEditorServices.Protocol/packages.config
@@ -1,5 +1,4 @@
-
\ No newline at end of file
diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj
index 4c5a278f4..c78b3f7bc 100644
--- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj
+++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj
@@ -38,18 +38,6 @@
bin\Release\Microsoft.PowerShell.EditorServices.XML
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll
- True
-
@@ -109,7 +97,12 @@
+
+
+
+
+
@@ -119,9 +112,6 @@
-
-
- {f4bde3d0-3eef-4157-8a3e-722df7adef60}
diff --git a/src/PowerShellEditorServices/Session/PowerShellContext.cs b/src/PowerShellEditorServices/Session/PowerShellContext.cs
index aab33fe39..4690e585a 100644
--- a/src/PowerShellEditorServices/Session/PowerShellContext.cs
+++ b/src/PowerShellEditorServices/Session/PowerShellContext.cs
@@ -5,7 +5,6 @@
using Microsoft.PowerShell.EditorServices.Console;
using Microsoft.PowerShell.EditorServices.Utility;
-using Nito.AsyncEx;
using System;
using System.Collections;
using System.Globalization;
@@ -44,8 +43,7 @@ public class PowerShellContext : IDisposable
private TaskCompletionSource pipelineResultTask;
private object runspaceMutex = new object();
- private RunspaceHandle currentRunspaceHandle;
- private IAsyncWaitQueue runspaceWaitQueue = new DefaultAsyncWaitQueue();
+ private AsyncQueue runspaceWaitQueue = new AsyncQueue();
#endregion
@@ -115,6 +113,7 @@ public PowerShellContext()
this.ownsInitialRunspace = true;
this.Initialize(runspace);
+
}
///
@@ -164,6 +163,10 @@ private void Initialize(Runspace initialRunspace)
#endif
this.SessionState = PowerShellContextState.Ready;
+
+ // Now that the runspace is ready, enqueue it for first use
+ RunspaceHandle runspaceHandle = new RunspaceHandle(this.currentRunspace, this);
+ this.runspaceWaitQueue.EnqueueAsync(runspaceHandle).Wait();
}
private Version GetPowerShellVersion()
@@ -198,21 +201,19 @@ private Version GetPowerShellVersion()
/// A RunspaceHandle instance that gives access to the session's runspace.
public Task GetRunspaceHandle()
{
- lock (this.runspaceMutex)
- {
- if (this.currentRunspaceHandle == null)
- {
- this.currentRunspaceHandle = new RunspaceHandle(this.currentRunspace, this);
- TaskCompletionSource tcs = new TaskCompletionSource();
- tcs.SetResult(this.currentRunspaceHandle);
- return tcs.Task;
- }
- else
- {
- // TODO: Use CancellationToken?
- return this.runspaceWaitQueue.Enqueue();
- }
- }
+ return this.GetRunspaceHandle(CancellationToken.None);
+ }
+
+ ///
+ /// Gets a RunspaceHandle for the session's runspace. This
+ /// handle is used to gain temporary ownership of the runspace
+ /// so that commands can be executed against it directly.
+ ///
+ /// A CancellationToken that can be used to cancel the request.
+ /// A RunspaceHandle instance that gives access to the session's runspace.
+ public Task GetRunspaceHandle(CancellationToken cancellationToken)
+ {
+ return this.runspaceWaitQueue.DequeueAsync(cancellationToken);
}
///
@@ -532,30 +533,17 @@ internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle)
{
Validate.IsNotNull("runspaceHandle", runspaceHandle);
- IDisposable dequeuedTask = null;
-
- lock (this.runspaceMutex)
+ if (this.runspaceWaitQueue.IsEmpty)
{
- if (runspaceHandle != this.currentRunspaceHandle)
- {
- throw new InvalidOperationException("Released runspace handle was not the current handle.");
- }
-
- this.currentRunspaceHandle = null;
-
- if (!this.runspaceWaitQueue.IsEmpty)
- {
- this.currentRunspaceHandle = new RunspaceHandle(this.currentRunspace, this);
- dequeuedTask =
- this.runspaceWaitQueue.Dequeue(
- this.currentRunspaceHandle);
- }
+ var newRunspaceHandle = new RunspaceHandle(this.currentRunspace, this);
+ this.runspaceWaitQueue.EnqueueAsync(newRunspaceHandle).Wait();
}
-
- // If a queued task was dequeued, call Dispose to cause it to be executed.
- if (dequeuedTask != null)
+ else
{
- dequeuedTask.Dispose();
+ // Write the situation to the log since this shouldn't happen
+ Logger.Write(
+ LogLevel.Error,
+ "The PowerShellContext.runspaceWaitQueue has more than one item");
}
}
diff --git a/src/PowerShellEditorServices/Utility/AsyncContext.cs b/src/PowerShellEditorServices/Utility/AsyncContext.cs
new file mode 100644
index 000000000..421ca3d96
--- /dev/null
+++ b/src/PowerShellEditorServices/Utility/AsyncContext.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerShell.EditorServices.Utility
+{
+ ///
+ /// Simplifies the setup of a SynchronizationContext for the use
+ /// of async calls in the current thread.
+ ///
+ public static class AsyncContext
+ {
+ ///
+ /// Starts a new ThreadSynchronizationContext, attaches it to
+ /// the thread, and then runs the given async main function.
+ ///
+ ///
+ /// The Task-returning Func which represents the "main" function
+ /// for the thread.
+ ///
+ public static void Start(Func asyncMainFunc)
+ {
+ // Is there already a synchronization context?
+ if (SynchronizationContext.Current != null)
+ {
+ throw new InvalidOperationException(
+ "A SynchronizationContext is already assigned on this thread.");
+ }
+
+ // Create and register a synchronization context for this thread
+ var threadSyncContext = new ThreadSynchronizationContext();
+ SynchronizationContext.SetSynchronizationContext(threadSyncContext);
+
+ // Get the main task and act on its completion
+ Task asyncMainTask = asyncMainFunc();
+ asyncMainTask.ContinueWith(
+ t => threadSyncContext.EndLoop(),
+ TaskScheduler.Default);
+
+ // Start the synchronization context's request loop and
+ // wait for the main task to complete
+ threadSyncContext.RunLoopOnCurrentThread();
+ asyncMainTask.GetAwaiter().GetResult();
+ }
+ }
+}
+
diff --git a/src/PowerShellEditorServices/Utility/AsyncContextThread.cs b/src/PowerShellEditorServices/Utility/AsyncContextThread.cs
new file mode 100644
index 000000000..92629437a
--- /dev/null
+++ b/src/PowerShellEditorServices/Utility/AsyncContextThread.cs
@@ -0,0 +1,85 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerShell.EditorServices.Utility
+{
+ ///
+ /// Provides a simplified interface for creating a new thread
+ /// and establishing an AsyncContext in it.
+ ///
+ public class AsyncContextThread
+ {
+ #region Private Fields
+
+ private Task threadTask;
+ private string threadName;
+ private CancellationTokenSource threadCancellationToken =
+ new CancellationTokenSource();
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the AsyncContextThread class.
+ ///
+ ///
+ /// The name of the thread for debugging purposes.
+ ///
+ public AsyncContextThread(string threadName)
+ {
+ this.threadName = threadName;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Runs a task on the AsyncContextThread.
+ ///
+ ///
+ /// A Func which returns the task to be run on the thread.
+ ///
+ ///
+ /// A Task which can be used to monitor the thread for completion.
+ ///
+ public Task Run(Func taskReturningFunc)
+ {
+ // Start up a long-running task with the action as the
+ // main entry point for the thread
+ this.threadTask =
+ Task.Factory.StartNew(
+ () =>
+ {
+ // Set the thread's name to help with debugging
+ Thread.CurrentThread.Name = "AsyncContextThread: " + this.threadName;
+
+ // Set up an AsyncContext to run the task
+ AsyncContext.Start(taskReturningFunc);
+ },
+ this.threadCancellationToken.Token,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
+
+ return this.threadTask;
+ }
+
+ ///
+ /// Stops the thread task.
+ ///
+ public void Stop()
+ {
+ this.threadCancellationToken.Cancel();
+ }
+
+ #endregion
+ }
+}
+
diff --git a/src/PowerShellEditorServices/Utility/AsyncLock.cs b/src/PowerShellEditorServices/Utility/AsyncLock.cs
new file mode 100644
index 000000000..eee894d9c
--- /dev/null
+++ b/src/PowerShellEditorServices/Utility/AsyncLock.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerShell.EditorServices.Utility
+{
+ ///
+ /// Provides a simple wrapper over a SemaphoreSlim to allow
+ /// synchronization locking inside of async calls. Cannot be
+ /// used recursively.
+ ///
+ public class AsyncLock
+ {
+ #region Fields
+
+ private Task lockReleaseTask;
+ private SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1);
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the AsyncLock class.
+ ///
+ public AsyncLock()
+ {
+ this.lockReleaseTask =
+ Task.FromResult(
+ (IDisposable)new LockReleaser(this));
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Locks
+ ///
+ /// A task which has an IDisposable
+ public Task LockAsync()
+ {
+ return this.LockAsync(CancellationToken.None);
+ }
+
+ ///
+ /// Obtains or waits for a lock which can be used to synchronize
+ /// access to a resource. The wait may be cancelled with the
+ /// given CancellationToken.
+ ///
+ ///
+ /// A CancellationToken which can be used to cancel the lock.
+ ///
+ ///
+ public Task LockAsync(CancellationToken cancellationToken)
+ {
+ Task waitTask = lockSemaphore.WaitAsync(cancellationToken);
+
+ return waitTask.IsCompleted ?
+ this.lockReleaseTask :
+ waitTask.ContinueWith(
+ (t, releaser) =>
+ {
+ return (IDisposable)releaser;
+ },
+ this.lockReleaseTask.Result,
+ cancellationToken,
+ TaskContinuationOptions.ExecuteSynchronously,
+ TaskScheduler.Default);
+ }
+
+ #endregion
+
+ #region Private Classes
+
+ ///
+ /// Provides an IDisposable wrapper around an AsyncLock so
+ /// that it can easily be used inside of a 'using' block.
+ ///
+ private class LockReleaser : IDisposable
+ {
+ private AsyncLock lockToRelease;
+
+ internal LockReleaser(AsyncLock lockToRelease)
+ {
+ this.lockToRelease = lockToRelease;
+ }
+
+ public void Dispose()
+ {
+ this.lockToRelease.lockSemaphore.Release();
+ }
+ }
+
+ #endregion
+ }
+}
+
diff --git a/src/PowerShellEditorServices/Utility/AsyncQueue.cs b/src/PowerShellEditorServices/Utility/AsyncQueue.cs
new file mode 100644
index 000000000..c26265b52
--- /dev/null
+++ b/src/PowerShellEditorServices/Utility/AsyncQueue.cs
@@ -0,0 +1,149 @@
+//
+// Copyright (c) Microsoft. 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.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerShell.EditorServices.Utility
+{
+ ///
+ /// Provides a synchronized queue which can be used from within async
+ /// operations. This is primarily used for producer/consumer scenarios.
+ ///
+ /// The type of item contained in the queue.
+ public class AsyncQueue
+ {
+ #region Private Fields
+
+ private AsyncLock queueLock = new AsyncLock();
+ private Queue itemQueue;
+ private Queue> requestQueue;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Returns true if the queue is currently empty.
+ ///
+ public bool IsEmpty { get; private set; }
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes an empty instance of the AsyncQueue class.
+ ///
+ public AsyncQueue() : this(Enumerable.Empty())
+ {
+ }
+
+ ///
+ /// Initializes an instance of the AsyncQueue class, pre-populated
+ /// with the given collection of items.
+ ///
+ ///
+ /// An IEnumerable containing the initial items with which the queue will
+ /// be populated.
+ ///
+ public AsyncQueue(IEnumerable initialItems)
+ {
+ this.itemQueue = new Queue(initialItems);
+ this.requestQueue = new Queue>();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Enqueues an item onto the end of the queue.
+ ///
+ /// The item to be added to the queue.
+ ///
+ /// A Task which can be awaited until the synchronized enqueue
+ /// operation completes.
+ ///
+ public async Task EnqueueAsync(T item)
+ {
+ using (await queueLock.LockAsync())
+ {
+ if (this.requestQueue.Count > 0)
+ {
+ // There are requests waiting, immediately dispatch the item
+ TaskCompletionSource requestTaskSource = this.requestQueue.Dequeue();
+ requestTaskSource.SetResult(item);
+ }
+ else
+ {
+ // No requests waiting, queue the item for a later request
+ this.itemQueue.Enqueue(item);
+ this.IsEmpty = false;
+ }
+ }
+ }
+
+ ///
+ /// Dequeues an item from the queue or waits asynchronously
+ /// until an item is available.
+ ///
+ ///
+ /// A Task which can be awaited until a value can be dequeued.
+ ///
+ public Task DequeueAsync()
+ {
+ return this.DequeueAsync(CancellationToken.None);
+ }
+
+ ///
+ /// Dequeues an item from the queue or waits asynchronously
+ /// until an item is available. The wait can be cancelled
+ /// using the given CancellationToken.
+ ///
+ ///
+ /// A CancellationToken with which a dequeue wait can be cancelled.
+ ///
+ ///
+ /// A Task which can be awaited until a value can be dequeued.
+ ///
+ public async Task DequeueAsync(CancellationToken cancellationToken)
+ {
+ Task requestTask;
+
+ using (await queueLock.LockAsync(cancellationToken))
+ {
+ if (this.itemQueue.Count > 0)
+ {
+ // Items are waiting to be taken so take one immediately
+ T item = this.itemQueue.Dequeue();
+ this.IsEmpty = this.itemQueue.Count == 0;
+
+ return item;
+ }
+ else
+ {
+ // Queue the request for the next item
+ var requestTaskSource = new TaskCompletionSource();
+ this.requestQueue.Enqueue(requestTaskSource);
+
+ // Register the wait task for cancel notifications
+ cancellationToken.Register(
+ () => requestTaskSource.TrySetCanceled());
+
+ requestTask = requestTaskSource.Task;
+ }
+ }
+
+ // Wait for the request task to complete outside of the lock
+ return await requestTask;
+ }
+
+ #endregion
+ }
+}
+
diff --git a/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs b/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs
new file mode 100644
index 000000000..03b57bee3
--- /dev/null
+++ b/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs
@@ -0,0 +1,77 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Microsoft.PowerShell.EditorServices.Utility
+{
+ ///
+ /// Provides a SynchronizationContext implementation that can be used
+ /// in console applications or any thread which doesn't have its
+ /// own SynchronizationContext.
+ ///
+ public class ThreadSynchronizationContext : SynchronizationContext
+ {
+ #region Private Fields
+
+ private BlockingCollection> requestQueue =
+ new BlockingCollection>();
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Posts a request for execution to the SynchronizationContext.
+ /// This will be executed on the SynchronizationContext's thread.
+ ///
+ ///
+ /// The callback to be invoked on the SynchronizationContext's thread.
+ ///
+ ///
+ /// A state object to pass along to the callback when executed through
+ /// the SynchronizationContext.
+ ///
+ public override void Post(SendOrPostCallback callback, object state)
+ {
+ // Add the request to the queue
+ this.requestQueue.Add(
+ new Tuple(
+ callback, state));
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Starts the SynchronizationContext message loop on the current thread.
+ ///
+ public void RunLoopOnCurrentThread()
+ {
+ Tuple request;
+
+ while (this.requestQueue.TryTake(out request, Timeout.Infinite))
+ {
+ // Invoke the request's callback
+ request.Item1(request.Item2);
+ }
+ }
+
+ ///
+ /// Ends the SynchronizationContext message loop.
+ ///
+ public void EndLoop()
+ {
+ // Tell the blocking queue that we're done
+ this.requestQueue.CompleteAdding();
+ }
+
+ #endregion
+ }
+}
+
diff --git a/src/PowerShellEditorServices/packages.config b/src/PowerShellEditorServices/packages.config
deleted file mode 100644
index d27aecea3..000000000
--- a/src/PowerShellEditorServices/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj b/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj
index 0a600ce67..373881fec 100644
--- a/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj
+++ b/test/PowerShellEditorServices.Test.Host/PowerShellEditorServices.Test.Host.csproj
@@ -39,18 +39,6 @@
..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dllTrue
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll
- True
-
-
- ..\..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll
- True
-
diff --git a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs
index 4ec18efc6..c54ca5886 100644
--- a/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs
+++ b/test/PowerShellEditorServices.Test.Host/ServerTestsBase.cs
@@ -1,6 +1,11 @@
-using Microsoft.PowerShell.EditorServices.Protocol.Client;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using Microsoft.PowerShell.EditorServices.Protocol.Client;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
-using Nito.AsyncEx;
+using Microsoft.PowerShell.EditorServices.Utility;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
@@ -11,8 +16,8 @@ public class ServerTestsBase
{
protected ProtocolClient protocolClient;
- private ConcurrentDictionary> eventQueuePerType =
- new ConcurrentDictionary>();
+ private ConcurrentDictionary> eventQueuePerType =
+ new ConcurrentDictionary>();
protected Task SendRequest(
RequestType requestType,
@@ -37,7 +42,7 @@ protected void QueueEventsForType(EventType eventType)
var eventQueue =
this.eventQueuePerType.AddOrUpdate(
eventType.MethodName,
- new AsyncProducerConsumerQueue