From 43a169ce4973ba54e1c45fa9009571215d40d7ab Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Thu, 15 Oct 2015 09:14:41 -0700 Subject: [PATCH] Use ConfigureAwait(false) throughout RHost & RSession code to avoid unnecessary thread affinity, and prevent deadlocks when async methods are called synchronously from the UI thread. --- src/Host/Client/Impl/RHost.cs | 46 +++++++++---------- src/Package/Impl/Repl/Session/RSession.cs | 30 ++++++------ .../Session/RSessionInteractionCommands.cs | 2 +- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Host/Client/Impl/RHost.cs b/src/Host/Client/Impl/RHost.cs index 0f79e8842..a8c0a5449 100644 --- a/src/Host/Client/Impl/RHost.cs +++ b/src/Host/Client/Impl/RHost.cs @@ -58,17 +58,17 @@ public void Dispose() { var uri = new Uri("ws://localhost:" + DefaultPort); for (int i = 0; ; ++i) { try { - await _socket.ConnectAsync(uri, ct); + await _socket.ConnectAsync(uri, ct).ConfigureAwait(false); break; } catch (WebSocketException) { if (i > 10) { throw; } - await Task.Delay(100, ct); + await Task.Delay(100, ct).ConfigureAwait(false); } } - await Run(ct); + await Run(ct).ConfigureAwait(false); } } catch (Exception ex) when (!(ex is OperationCanceledException)) { // TODO: replace with better logging Trace.Fail("Exception in RHost run loop:\n" + ex); @@ -88,8 +88,8 @@ public void Dispose() { public async Task AttachAndRun(Uri uri, CancellationToken ct = default(CancellationToken)) { ct = CancellationTokenSource.CreateLinkedTokenSource(ct, _cts.Token).Token; using (_socket = new ClientWebSocket()) { - await _socket.ConnectAsync(uri, ct); - await Run(ct); + await _socket.ConnectAsync(uri, ct).ConfigureAwait(false); + await Run(ct).ConfigureAwait(false); } } @@ -102,16 +102,16 @@ private async Task Run(CancellationToken ct) { _isRunning = true; try { try { - var webSocketReceiveResult = await _socket.ReceiveAsync(new ArraySegment(_buffer), ct); + var webSocketReceiveResult = await _socket.ReceiveAsync(new ArraySegment(_buffer), ct).ConfigureAwait(false); string s = Encoding.UTF8.GetString(_buffer, 0, webSocketReceiveResult.Count); var obj = JObject.Parse(s); int protocolVersion = (int)(double)obj["protocol_version"]; Debug.Assert(protocolVersion == 1); string rVersion = (string)obj["R_version"]; - await _callbacks.Connected(rVersion); - await RunLoop(ct, allowEval: true); + await _callbacks.Connected(rVersion).ConfigureAwait(false); + await RunLoop(ct, allowEval: true).ConfigureAwait(false); } finally { - await _callbacks.Disconnected(); + await _callbacks.Disconnected().ConfigureAwait(false); } } finally { _isRunning = false; @@ -123,7 +123,7 @@ private async Task RunLoop(CancellationToken ct, bool allowEval) { WebSocketReceiveResult webSocketReceiveResult; var s = string.Empty; do { - webSocketReceiveResult = await _socket.ReceiveAsync(new ArraySegment(_buffer), ct); + webSocketReceiveResult = await _socket.ReceiveAsync(new ArraySegment(_buffer), ct).ConfigureAwait(false); if (webSocketReceiveResult.CloseStatus != null) { return null; } @@ -136,24 +136,24 @@ private async Task RunLoop(CancellationToken ct, bool allowEval) { var evt = (string)obj["event"]; switch (evt) { case "YesNoCancel": - await YesNoCancel(contexts, obj, allowEval, ct); + await YesNoCancel(contexts, obj, allowEval, ct).ConfigureAwait(false); break; case "ReadConsole": - await ReadConsole(contexts, obj, allowEval, ct); + await ReadConsole(contexts, obj, allowEval, ct).ConfigureAwait(false); break; case "WriteConsoleEx": await - _callbacks.WriteConsoleEx(contexts, (string)obj["buf"], (OutputType)(double)obj["otype"], ct); + _callbacks.WriteConsoleEx(contexts, (string)obj["buf"], (OutputType)(double)obj["otype"], ct).ConfigureAwait(false); break; case "ShowMessage": - await _callbacks.ShowMessage(contexts, (string)obj["s"], ct); + await _callbacks.ShowMessage(contexts, (string)obj["s"], ct).ConfigureAwait(false); break; case "Busy": - await _callbacks.Busy(contexts, (bool)obj["which"], ct); + await _callbacks.Busy(contexts, (bool)obj["which"], ct).ConfigureAwait(false); break; case "CallBack": @@ -163,7 +163,7 @@ private async Task RunLoop(CancellationToken ct, bool allowEval) { return obj; case "PlotXaml": - await _callbacks.PlotXaml(contexts, (string)obj["filepath"], ct); + await _callbacks.PlotXaml(contexts, (string)obj["filepath"], ct).ConfigureAwait(false); // TODO: delete temporary xaml and bitmap files break; @@ -181,8 +181,8 @@ private async Task RunLoop(CancellationToken ct, bool allowEval) { private async Task YesNoCancel(RContext[] contexts, JObject obj, bool allowEval, CancellationToken ct) { try { _canEval = allowEval; - YesNoCancel input = await _callbacks.YesNoCancel(contexts, (string)obj["s"], _canEval, ct); - await SendAsync((double)input, ct); + YesNoCancel input = await _callbacks.YesNoCancel(contexts, (string)obj["s"], _canEval, ct).ConfigureAwait(false); + await SendAsync((double)input, ct).ConfigureAwait(false); } finally { _canEval = false; } @@ -197,9 +197,9 @@ private async Task ReadConsole(RContext[] contexts, JObject obj, bool allowEval, var len = (int)(double)obj["len"]; var addToHistory = (bool)obj["addToHistory"]; - string input = await _callbacks.ReadConsole(contexts, prompt, buf, len, addToHistory, _canEval, ct); + string input = await _callbacks.ReadConsole(contexts, prompt, buf, len, addToHistory, _canEval, ct).ConfigureAwait(false); input = input.Replace("\r\n", "\n"); - await SendAsync(input, ct); + await SendAsync(input, ct).ConfigureAwait(false); } finally { _canEval = false; } @@ -212,7 +212,7 @@ private async Task SendAsync(T input, CancellationToken ct) { var response = JsonConvert.SerializeObject(input); byte[] responseBytes = Encoding.UTF8.GetBytes(response); - await _socket.SendAsync(new ArraySegment(responseBytes, 0, responseBytes.Length), WebSocketMessageType.Text, true, ct); + await _socket.SendAsync(new ArraySegment(responseBytes, 0, responseBytes.Length), WebSocketMessageType.Text, true, ct).ConfigureAwait(false); } private static RContext[] GetContexts(JObject obj) { @@ -240,9 +240,9 @@ async Task IRExpressionEvaluator.EvaluateAsync(string express }); var requestBytes = Encoding.UTF8.GetBytes(request); - await _socket.SendAsync(new ArraySegment(requestBytes, 0, requestBytes.Length), WebSocketMessageType.Text, true, ct); + await _socket.SendAsync(new ArraySegment(requestBytes, 0, requestBytes.Length), WebSocketMessageType.Text, true, ct).ConfigureAwait(false); - var obj = await RunLoop(ct, reentrant); + var obj = await RunLoop(ct, reentrant).ConfigureAwait(false); JToken result, error, parseStatus; obj.TryGetValue("result", out result); diff --git a/src/Package/Impl/Repl/Session/RSession.cs b/src/Package/Impl/Repl/Session/RSession.cs index f2cf0a373..5fc484832 100644 --- a/src/Package/Impl/Repl/Session/RSession.cs +++ b/src/Package/Impl/Repl/Session/RSession.cs @@ -70,9 +70,9 @@ public Task StartHostAsync() { _hostRunTask = Task.Run(() => _host.CreateAndRun(RToolsSettings.GetRVersionPath())); this.ScheduleEvaluation(async e => { - await e.SetVsGraphicsDevice(); - await e.SetDefaultWorkingDirectory(); - await e.PrepareDataInspect(); + await e.SetVsGraphicsDevice().ConfigureAwait(false); + await e.SetDefaultWorkingDirectory().ConfigureAwait(false); + await e.PrepareDataInspect().ConfigureAwait(false); }); var initializationTask = _initializationTcs.Task; @@ -85,14 +85,14 @@ public async Task StopHostAsync() { return; } - var request = await BeginInteractionAsync(false); + var request = await BeginInteractionAsync(false).ConfigureAwait(false); if (_hostRunTask.IsCompleted) { request.Dispose(); return; } - await request.Quit(); - await _hostRunTask; + await request.Quit().ConfigureAwait(false); + await _hostRunTask.ConfigureAwait(false); } Task IRCallbacks.Connected(string rVersion) { @@ -139,7 +139,7 @@ async Task IRCallbacks.ReadConsole(IReadOnlyList contexts, st Task evaluationTask; if (isEvaluationAllowed) { - await EvaluateAll(contexts, ct); + await EvaluateAll(contexts, ct).ConfigureAwait(false); evaluationCts = new CancellationTokenSource(); evaluationTask = EvaluateUntilCancelled(contexts, evaluationCts.Token, ct); } else { @@ -155,7 +155,7 @@ async Task IRCallbacks.ReadConsole(IReadOnlyList contexts, st ct.ThrowIfCancellationRequested(); try { - consoleInput = await ReadNextRequest(prompt, len, ct); + consoleInput = await ReadNextRequest(prompt, len, ct).ConfigureAwait(false); } catch (TaskCanceledException) { // If request was canceled through means other than our token, it indicates the refusal of // that requestor to respond to that particular prompt, so move on to the next requestor. @@ -166,7 +166,7 @@ async Task IRCallbacks.ReadConsole(IReadOnlyList contexts, st // If evaluation was allowed, cancel evaluation processing but await evaluation that is in progress evaluationCts?.Cancel(); - await evaluationTask; + await evaluationTask.ConfigureAwait(false); OnAfterRequest(contexts, prompt, len, addToHistory); @@ -174,7 +174,7 @@ async Task IRCallbacks.ReadConsole(IReadOnlyList contexts, st } private async Task ReadNextRequest(string prompt, int len, CancellationToken ct) { - var requestSource = await _pendingRequestSources.ReceiveAsync(ct); + var requestSource = await _pendingRequestSources.ReceiveAsync(ct).ConfigureAwait(false); TaskCompletionSource requestTcs = new TaskCompletionSource(); Interlocked.Exchange(ref _currentRequestSource, requestSource); @@ -182,7 +182,7 @@ private async Task ReadNextRequest(string prompt, int len, CancellationT requestSource.Request(prompt, len, requestTcs); ct.Register(delegate { requestTcs.TrySetCanceled(); }); - string response = await requestTcs.Task; + string response = await requestTcs.Task.ConfigureAwait(false); Debug.Assert(response.Length < len); // len includes null terminator if (response.Length >= len) { @@ -197,8 +197,8 @@ private async Task EvaluateUntilCancelled(IReadOnlyList contexts, Can while (!ct.IsCancellationRequested) { try { - var evaluationSource = await _pendingEvaluationSources.ReceiveAsync(ct); - await evaluationSource.BeginEvaluationAsync(contexts, _host, hostCancellationToken); + var evaluationSource = await _pendingEvaluationSources.ReceiveAsync(ct).ConfigureAwait(false); + await evaluationSource.BeginEvaluationAsync(contexts, _host, hostCancellationToken).ConfigureAwait(false); } catch (TaskCanceledException) { return; } @@ -208,7 +208,7 @@ private async Task EvaluateUntilCancelled(IReadOnlyList contexts, Can private async Task EvaluateAll(IReadOnlyList contexts, CancellationToken ct) { RSessionEvaluationSource source; while (!ct.IsCancellationRequested && _pendingEvaluationSources.TryReceive(out source)) { - await source.BeginEvaluationAsync(contexts, _host, ct); + await source.BeginEvaluationAsync(contexts, _host, ct).ConfigureAwait(false); } } @@ -232,7 +232,7 @@ async Task IRCallbacks.ShowMessage(IReadOnlyList contexts, string mes async Task IRCallbacks.YesNoCancel(IReadOnlyList contexts, string s, bool isEvaluationAllowed, CancellationToken ct) { if (isEvaluationAllowed) { - await EvaluateAll(contexts, ct); + await EvaluateAll(contexts, ct).ConfigureAwait(false); } return YesNoCancel.Yes; diff --git a/src/Package/Impl/Repl/Session/RSessionInteractionCommands.cs b/src/Package/Impl/Repl/Session/RSessionInteractionCommands.cs index b360ec3d0..3b3e29651 100644 --- a/src/Package/Impl/Repl/Session/RSessionInteractionCommands.cs +++ b/src/Package/Impl/Repl/Session/RSessionInteractionCommands.cs @@ -4,7 +4,7 @@ namespace Microsoft.VisualStudio.R.Package.Repl.Session { public static class RSessionInteractionCommands { public static async Task Quit(this IRSessionInteraction interaction) { - await interaction.RespondAsync("q()\n"); + await interaction.RespondAsync("q()\n").ConfigureAwait(false); } } } \ No newline at end of file