From 4fef9f05554196adc15595f06e42060f61890b50 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 8 Sep 2015 21:15:51 -0300 Subject: [PATCH] Don't capture the UI thread for async methods. Use Task.Run instead of Task.Factory.StartNew. --- src/Discord.Net/API/DiscordAPI.cs | 6 +- src/Discord.Net/DiscordClient.API.cs | 63 +++++------ src/Discord.Net/DiscordClient.Cache.cs | 4 +- src/Discord.Net/DiscordClient.cs | 69 +++++------- src/Discord.Net/DiscordDataSocket.cs | 6 +- src/Discord.Net/DiscordVoiceSocket.cs | 110 +++++++++--------- src/Discord.Net/DiscordWebSocket.cs | 130 +++++++++++----------- src/Discord.Net/Helpers/Extensions.cs | 4 +- src/Discord.Net/Helpers/JsonHttpClient.cs | 14 +-- 9 files changed, 197 insertions(+), 209 deletions(-) diff --git a/src/Discord.Net/API/DiscordAPI.cs b/src/Discord.Net/API/DiscordAPI.cs index c8271bb65..894c609dd 100644 --- a/src/Discord.Net/API/DiscordAPI.cs +++ b/src/Discord.Net/API/DiscordAPI.cs @@ -21,15 +21,15 @@ namespace Discord.API => _http.Get(Endpoints.Gateway); public async Task LoginAnonymous(string username) { - var fingerprintResponse = await _http.Post(Endpoints.AuthFingerprint); + var fingerprintResponse = await _http.Post(Endpoints.AuthFingerprint).ConfigureAwait(false); var registerRequest = new APIRequests.AuthRegisterRequest { Fingerprint = fingerprintResponse.Fingerprint, Username = username }; - var registerResponse = await _http.Post(Endpoints.AuthRegister, registerRequest); + var registerResponse = await _http.Post(Endpoints.AuthRegister, registerRequest).ConfigureAwait(false); return registerResponse; } public async Task Login(string email, string password) { var request = new APIRequests.AuthLogin { Email = email, Password = password }; - var response = await _http.Post(Endpoints.AuthLogin, request); + var response = await _http.Post(Endpoints.AuthLogin, request).ConfigureAwait(false); return response; } public Task Logout() diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index 25ac7427a..e160d6215 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -24,7 +24,7 @@ namespace Discord if (name == null) throw new ArgumentNullException(nameof(name)); if (region == null) throw new ArgumentNullException(nameof(region)); - var response = await _api.CreateServer(name, region); + var response = await _api.CreateServer(name, region).ConfigureAwait(false); return _servers.Update(response.Id, response); } @@ -37,7 +37,7 @@ namespace Discord CheckReady(); if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - try { await _api.LeaveServer(serverId); } + try { await _api.LeaveServer(serverId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } return _servers.Remove(serverId); } @@ -54,7 +54,7 @@ namespace Discord if (name == null) throw new ArgumentNullException(nameof(name)); if (type == null) throw new ArgumentNullException(nameof(type)); - var response = await _api.CreateChannel(serverId, name, type); + var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); return _channels.Update(response.Id, serverId, response); } @@ -67,7 +67,7 @@ namespace Discord CheckReady(); if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - try { await _api.DestroyChannel(channelId); } + try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } return _channels.Remove(channelId); } @@ -108,7 +108,7 @@ namespace Discord if (serverId == null) throw new ArgumentNullException(nameof(serverId)); if (userId == null) throw new ArgumentNullException(nameof(userId)); - try { await _api.Unban(serverId, userId); } + try { await _api.Unban(serverId, userId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } @@ -139,7 +139,7 @@ namespace Discord if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); - var response = await _api.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass); + var response = await _api.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass).ConfigureAwait(false); _channels.Update(response.Channel.Id, response.Server.Id, response.Channel); _servers.Update(response.Server.Id, response.Server); _users.Update(response.Inviter.Id, response.Inviter); @@ -163,7 +163,7 @@ namespace Discord CheckReady(); if (id == null) throw new ArgumentNullException(nameof(id)); - var response = await _api.GetInvite(id); + var response = await _api.GetInvite(id).ConfigureAwait(false); return new Invite(response.Code, response.XkcdPass, this) { ChannelId = response.Channel.Id, @@ -195,8 +195,8 @@ namespace Discord id = id.Substring(0, id.Length - 1); //Check if this is a human-readable link and get its ID - var response = await _api.GetInvite(id); - await _api.AcceptInvite(response.Code); + var response = await _api.GetInvite(id).ConfigureAwait(false); + await _api.AcceptInvite(response.Code).ConfigureAwait(false); } /// Deletes the provided invite. @@ -208,8 +208,8 @@ namespace Discord try { //Check if this is a human-readable link and get its ID - var response = await _api.GetInvite(id); - await _api.DeleteInvite(response.Code); + var response = await _api.GetInvite(id).ConfigureAwait(false); + await _api.DeleteInvite(response.Code).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } @@ -257,31 +257,28 @@ namespace Discord } else { - var msg = await _api.SendMessage(channelId, blockText, mentions, nonce); + var msg = await _api.SendMessage(channelId, blockText, mentions, nonce).ConfigureAwait(false); result[i] = _messages.Update(msg.Id, channelId, msg); result[i].Nonce = nonce; try { RaiseMessageSent(result[i]); } catch { } } - await Task.Delay(1000); + await Task.Delay(1000).ConfigureAwait(false); } return result; } /// Sends a private message to the provided channel. public async Task SendPrivateMessage(User user, string text) - => await SendMessage(await GetPMChannel(user), text, new string[0]); + { + var channel = await GetPMChannel(user).ConfigureAwait(false); + return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); + } /// Sends a private message to the provided channel. public async Task SendPrivateMessage(string userId, string text) - => await SendMessage(await GetPMChannel(userId), text, new string[0]); - /*/// Sends a private message to the provided user, mentioning certain users. - /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public async Task SendPrivateMessage(User user, string text, string[] mentions) - => SendMessage(await GetOrCreatePMChannel(user), text, mentions); - /// Sends a private message to the provided user, mentioning certain users. - /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public async Task SendPrivateMessage(string userId, string text, string[] mentions) - => SendMessage(await GetOrCreatePMChannel(userId), text, mentions);*/ - + { + var channel = await GetPMChannel(userId).ConfigureAwait(false); + return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); + } /// Edits a message the provided message. public Task EditMessage(Message message, string text) @@ -313,7 +310,7 @@ namespace Discord if (text.Length > DiscordAPI.MaxMessageSize) text = text.Substring(0, DiscordAPI.MaxMessageSize); - var msg = await _api.EditMessage(channelId, messageId, text, mentions); + var msg = await _api.EditMessage(channelId, messageId, text, mentions).ConfigureAwait(false); _messages.Update(msg.Id, channelId, msg); } @@ -329,7 +326,7 @@ namespace Discord try { - await _api.DeleteMessage(channelId, msgId); + await _api.DeleteMessage(channelId, msgId).ConfigureAwait(false); _messages.Remove(msgId); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } @@ -343,7 +340,7 @@ namespace Discord { try { - await _api.DeleteMessage(msg.ChannelId, msg.Id); + await _api.DeleteMessage(msg.ChannelId, msg.Id).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } @@ -357,7 +354,7 @@ namespace Discord { try { - await _api.DeleteMessage(channelId, msgId); + await _api.DeleteMessage(channelId, msgId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } @@ -404,7 +401,7 @@ namespace Discord { try { - var msgs = await _api.GetMessages(channel.Id, count); + var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); return msgs.OrderBy(x => x.Timestamp) .Select(x => { @@ -503,21 +500,21 @@ namespace Discord public async Task ChangeUsername(string newName, string currentEmail, string currentPassword) { CheckReady(); - var response = await _api.ChangeUsername(newName, currentEmail, currentPassword); + var response = await _api.ChangeUsername(newName, currentEmail, currentPassword).ConfigureAwait(false); _users.Update(response.Id, response); } /// Changes your email to newEmail. public async Task ChangeEmail(string newEmail, string currentPassword) { CheckReady(); - var response = await _api.ChangeEmail(newEmail, currentPassword); + var response = await _api.ChangeEmail(newEmail, currentPassword).ConfigureAwait(false); _users.Update(response.Id, response); } /// Changes your password to newPassword. public async Task ChangePassword(string newPassword, string currentEmail, string currentPassword) { CheckReady(); - var response = await _api.ChangePassword(newPassword, currentEmail, currentPassword); + var response = await _api.ChangePassword(newPassword, currentEmail, currentPassword).ConfigureAwait(false); _users.Update(response.Id, response); } @@ -526,7 +523,7 @@ namespace Discord public async Task ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) { CheckReady(); - var response = await _api.ChangeAvatar(imageType, bytes, currentEmail, currentPassword); + var response = await _api.ChangeAvatar(imageType, bytes, currentEmail, currentPassword).ConfigureAwait(false); _users.Update(response.Id, response); } } diff --git a/src/Discord.Net/DiscordClient.Cache.cs b/src/Discord.Net/DiscordClient.Cache.cs index 5dea7a416..515eb18ed 100644 --- a/src/Discord.Net/DiscordClient.Cache.cs +++ b/src/Discord.Net/DiscordClient.Cache.cs @@ -403,14 +403,14 @@ namespace Discord var channel = user.PrivateChannel; if (channel != null) return channel; - return await CreatePMChannel(user?.Id); + return await CreatePMChannel(user?.Id).ConfigureAwait(false); } private async Task CreatePMChannel(string userId) { CheckReady(); if (userId == null) throw new ArgumentNullException(nameof(userId)); - var response = await _api.CreatePMChannel(_myId, userId); + var response = await _api.CreatePMChannel(_myId, userId).ConfigureAwait(false); return _channels.Update(response.Id, response); } diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index c24df4ceb..57b2d84ea 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -107,21 +107,21 @@ namespace Discord //Reconnect if we didn't cause the disconnect if (e.WasUnexpected) { - await Task.Delay(_config.ReconnectDelay); + await Task.Delay(_config.ReconnectDelay).ConfigureAwait(false); while (!_disconnectToken.IsCancellationRequested) { try { - await _webSocket.ReconnectAsync(); + await _webSocket.ReconnectAsync().ConfigureAwait(false); if (_http.Token != null) - await _webSocket.Login(_http.Token); + await _webSocket.Login(_http.Token).ConfigureAwait(false); break; } catch (Exception ex) { RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket reconnect failed: {ex.Message}"); //Net is down? We can keep trying to reconnect until the user runs Disconnect() - await Task.Delay(_config.FailedReconnectDelay); + await Task.Delay(_config.FailedReconnectDelay).ConfigureAwait(false); } } } @@ -141,12 +141,12 @@ namespace Discord //Reconnect if we didn't cause the disconnect if (e.WasUnexpected) { - await Task.Delay(_config.ReconnectDelay); + await Task.Delay(_config.ReconnectDelay).ConfigureAwait(false); while (!_disconnectToken.IsCancellationRequested) { try { - await _voiceWebSocket.ReconnectAsync(); + await _voiceWebSocket.ReconnectAsync().ConfigureAwait(false); break; } catch (Exception ex) @@ -154,7 +154,7 @@ namespace Discord if (_isDebugMode) RaiseOnDebugMessage(DebugMessageType.Connection, $"VoiceSocket reconnect failed: {ex.Message}"); //Net is down? We can keep trying to reconnect until the user runs Disconnect() - await Task.Delay(_config.FailedReconnectDelay); + await Task.Delay(_config.FailedReconnectDelay).ConfigureAwait(false); } } } @@ -426,7 +426,7 @@ namespace Discord if (_config.EnableVoice) { _voiceWebSocket.SetSessionData(data.ServerId, _myId, _sessionId, data.Token); - await _voiceWebSocket.ConnectAsync("wss://" + data.Endpoint.Split(':')[0]); + await _voiceWebSocket.ConnectAsync("wss://" + data.Endpoint.Split(':')[0]).ConfigureAwait(false); } #endif } @@ -469,7 +469,7 @@ namespace Discord APIResponses.SendMessage apiMsg = null; try { - apiMsg = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce); + apiMsg = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce).ConfigureAwait(false); } catch (WebException) { break; } catch (HttpException) { hasFailed = true; } @@ -483,7 +483,7 @@ namespace Discord msg.HasFailed = hasFailed; try { RaiseMessageSent(msg); } catch { } } - await Task.Delay(_config.MessageQueueInterval); + await Task.Delay(_config.MessageQueueInterval).ConfigureAwait(false); } } catch { } @@ -499,49 +499,36 @@ namespace Discord /// Connects to the Discord server with the provided token. public async Task Connect(string token) { - await Disconnect(); + await Disconnect().ConfigureAwait(false); if (_isDebugMode) RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket is using cached token."); - await ConnectInternal(token); + await ConnectInternal(token).ConfigureAwait(false); } /// Connects to the Discord server with the provided email and password. /// Returns a token for future connections. public async Task Connect(string email, string password) { - await Disconnect(); + await Disconnect().ConfigureAwait(false); - var response = await _api.Login(email, password); + var response = await _api.Login(email, password).ConfigureAwait(false); if (_isDebugMode) RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket got token."); - return await ConnectInternal(response.Token); + return await ConnectInternal(response.Token).ConfigureAwait(false); } - /// Connects to the Discord server as an anonymous user with the provided username. - /// Returns a token for future connections. - /*public async Task ConnectAnonymous(string username) - { - await Disconnect(); - - var response = await _api.LoginAnonymous(username); - if (_isDebugMode) - RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket got anonymous token."); - _http.Token = response.Token; - - return await ConnectInternal(response.Token); - }*/ private async Task ConnectInternal(string token) { _blockEvent.Reset(); _http.Token = token; - string url = (await _api.GetWebSocketEndpoint()).Url; + string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; if (_isDebugMode) RaiseOnDebugMessage(DebugMessageType.Connection, $"DataSocket got endpoint."); - await _webSocket.ConnectAsync(url); - await _webSocket.Login(token); + await _webSocket.ConnectAsync(url).ConfigureAwait(false); + await _webSocket.Login(token).ConfigureAwait(false); _disconnectToken = new CancellationTokenSource(); if (_config.UseMessageQueue) @@ -550,10 +537,10 @@ namespace Discord _mainTask = _disconnectToken.Wait(); _mainTask = _mainTask.ContinueWith(async x => { - await _webSocket.DisconnectAsync(); + await _webSocket.DisconnectAsync().ConfigureAwait(false); #if !DNXCORE50 if (_config.EnableVoice) - await _voiceWebSocket.DisconnectAsync(); + await _voiceWebSocket.DisconnectAsync().ConfigureAwait(false); #endif //Clear send queue @@ -569,7 +556,7 @@ namespace Discord _blockEvent.Set(); _mainTask = null; }).Unwrap(); - _isConnected = true; + _isConnected = true; return token; } /// Disconnects from the Discord server, canceling any pending requests. @@ -578,7 +565,7 @@ namespace Discord if (_mainTask != null) { try { _disconnectToken.Cancel(); } catch (NullReferenceException) { } - try { await _mainTask; } catch (NullReferenceException) { } + try { await _mainTask.ConfigureAwait(false); } catch (NullReferenceException) { } } } @@ -591,13 +578,13 @@ namespace Discord CheckVoice(); if (channel == null) throw new ArgumentNullException(nameof(channel)); - await LeaveVoiceServer(); + await LeaveVoiceServer().ConfigureAwait(false); //_currentVoiceServerId = channel.ServerId; _webSocket.JoinVoice(channel); #if !DNXCORE50 - await _voiceWebSocket.BeginConnect(); + await _voiceWebSocket.BeginConnect().ConfigureAwait(false); #else - await Task.CompletedTask; + await Task.CompletedTask.ConfigureAwait(false); #endif } @@ -607,9 +594,9 @@ namespace Discord CheckVoice(); #if !DNXCORE50 - await _voiceWebSocket.DisconnectAsync(); + await _voiceWebSocket.DisconnectAsync().ConfigureAwait(false); #else - await Task.CompletedTask; + await Task.CompletedTask.ConfigureAwait(false); #endif //if (_voiceWebSocket.CurrentVoiceServerId != null) _webSocket.LeaveVoice(); @@ -654,7 +641,7 @@ namespace Discord #if !DNXCORE50 _voiceWebSocket.Wait(); #endif - await TaskHelper.CompletedTask; + await TaskHelper.CompletedTask.ConfigureAwait(false); } //Helpers diff --git a/src/Discord.Net/DiscordDataSocket.cs b/src/Discord.Net/DiscordDataSocket.cs index 4430867ca..ec9331d15 100644 --- a/src/Discord.Net/DiscordDataSocket.cs +++ b/src/Discord.Net/DiscordDataSocket.cs @@ -27,8 +27,8 @@ namespace Discord _lastSeq = 0; _lastSession = null; _redirectServer = null; - await BeginConnect(); - await base.ConnectAsync(url); + await BeginConnect().ConfigureAwait(false); + await base.ConnectAsync(url).ConfigureAwait(false); } public async Task Login(string token) { @@ -44,7 +44,7 @@ namespace Discord msg.Payload.Properties["$device"] = "Discord.Net"; msg.Payload.Properties["$referrer"] = ""; msg.Payload.Properties["$referring_domain"] = ""; - await SendMessage(msg, cancelToken); + await SendMessage(msg, cancelToken).ConfigureAwait(false); try { diff --git a/src/Discord.Net/DiscordVoiceSocket.cs b/src/Discord.Net/DiscordVoiceSocket.cs index 190cc2229..771abee8e 100644 --- a/src/Discord.Net/DiscordVoiceSocket.cs +++ b/src/Discord.Net/DiscordVoiceSocket.cs @@ -14,6 +14,7 @@ using System.Threading; using System.Threading.Tasks; using System.Text; using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage; +using Discord.Helpers; namespace Discord { @@ -65,29 +66,26 @@ namespace Discord _isClearing = false; var cancelToken = _disconnectToken.Token; - Task.Factory.StartNew(async () => + Task.Run(async () => { - _connectWaitOnLogin.Reset(); - - VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); - msg.Payload.ServerId = _serverId; - msg.Payload.SessionId = _sessionId; - msg.Payload.Token = _token; - msg.Payload.UserId = _userId; - await SendMessage(msg, cancelToken); - try { + _connectWaitOnLogin.Reset(); + + VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); + msg.Payload.ServerId = _serverId; + msg.Payload.SessionId = _sessionId; + msg.Payload.Token = _token; + msg.Payload.UserId = _userId; + await SendMessage(msg, cancelToken).ConfigureAwait(false); + if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) return; - } - catch (OperationCanceledException) - { - return; - } - SetConnected(); - }); + SetConnected(); + } + catch (OperationCanceledException) { } + }, _disconnectToken.Token); } protected override void OnDisconnect() { @@ -128,41 +126,45 @@ namespace Discord public new async Task BeginConnect() { - await base.BeginConnect(); + await base.BeginConnect().ConfigureAwait(false); var cancelToken = _disconnectToken.Token; - await Task.Yield(); - try - { - if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on JoinServer message - throw new Exception("No reply from Discord server"); - } - catch (OperationCanceledException) + await Task.Factory.StartNew(() => { - if (_disconnectReason == null) - throw new Exception("An unknown websocket error occurred."); - else - _disconnectReason.Throw(); - } + try + { + if (!_connectWaitOnLogin.Wait(_timeout, cancelToken)) //Waiting on JoinServer message + throw new Exception("No reply from Discord server"); + } + catch (OperationCanceledException) + { + if (_disconnectReason == null) + throw new Exception("An unknown websocket error occurred."); + else + _disconnectReason.Throw(); + } + }).ConfigureAwait(false); } private async Task ReceiveVoiceAsync() { var cancelSource = _disconnectToken; var cancelToken = cancelSource.Token; - await Task.Yield(); - try + await Task.Run(async () => { - while (!cancelToken.IsCancellationRequested) + try { - var result = await _udp.ReceiveAsync(); - ProcessUdpMessage(result); + while (!cancelToken.IsCancellationRequested) + { + var result = await _udp.ReceiveAsync().ConfigureAwait(false); + ProcessUdpMessage(result); + } } - } - catch (OperationCanceledException) { } - catch (ObjectDisposedException) { } - catch (Exception ex) { DisconnectInternal(ex); } + catch (OperationCanceledException) { } + catch (ObjectDisposedException) { } + catch (Exception ex) { DisconnectInternal(ex); } + }).ConfigureAwait(false); } #if USE_THREAD @@ -170,11 +172,12 @@ namespace Discord { var cancelToken = cancelSource.Token; #else - private async Task SendVoiceAsync() + private Task SendVoiceAsync() { var cancelSource = _disconnectToken; var cancelToken = cancelSource.Token; - await Task.Yield(); + return Task.Run(() => + { #endif byte[] packet; @@ -224,7 +227,7 @@ namespace Discord #if USE_THREAD _udp.Send(rtpPacket, packet.Length + 12); #else - await _udp.SendAsync(rtpPacket, packet.Length + 12); + await _udp.SendAsync(rtpPacket, packet.Length + 12).ConfigureAwait(false); #endif } timestamp = unchecked(timestamp + samplesPerFrame); @@ -247,24 +250,23 @@ namespace Discord #if USE_THREAD Thread.Sleep(1); #else - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); #endif } } catch (OperationCanceledException) { } catch (ObjectDisposedException) { } catch (Exception ex) { DisconnectInternal(ex); } - } - //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken - private async Task WatcherAsync() +#if !USE_THREAD + }).ConfigureAwait(false); +#endif + } + //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken + private Task WatcherAsync() { var cancelToken = _disconnectToken.Token; - try - { - await Task.Delay(-1, cancelToken); - } - catch (OperationCanceledException) { } - finally { _udp.Close(); } + return cancelToken.Wait() + .ContinueWith(_ => _udp.Close()); } protected override async Task ProcessMessage(string json) @@ -279,7 +281,7 @@ namespace Discord var payload = (msg.Payload as JToken).ToObject(); _heartbeatInterval = payload.HeartbeatInterval; _ssrc = payload.SSRC; - _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(_host.Replace("wss://", ""))).FirstOrDefault(), payload.Port); + _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(_host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); //_mode = payload.Modes.LastOrDefault(); _mode = "plain"; _udp.Connect(_endpoint); @@ -295,7 +297,7 @@ namespace Discord 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }, 70); + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }, 70).ConfigureAwait(false); } } break; diff --git a/src/Discord.Net/DiscordWebSocket.cs b/src/Discord.Net/DiscordWebSocket.cs index 8a5acdc0e..f659c0fa3 100644 --- a/src/Discord.Net/DiscordWebSocket.cs +++ b/src/Discord.Net/DiscordWebSocket.cs @@ -42,19 +42,17 @@ namespace Discord protected virtual async Task BeginConnect() { - await DisconnectAsync(); + await DisconnectAsync().ConfigureAwait(false); _disconnectToken = new CancellationTokenSource(); _disconnectReason = null; } public virtual async Task ConnectAsync(string url) { - //await DisconnectAsync(); - var cancelToken = _disconnectToken.Token; _webSocket = new ClientWebSocket(); _webSocket.Options.KeepAliveInterval = TimeSpan.Zero; - await _webSocket.ConnectAsync(new Uri(url), cancelToken); + await _webSocket.ConnectAsync(new Uri(url), cancelToken).ConfigureAwait(false); _host = url; if (_isDebug) @@ -99,7 +97,7 @@ namespace Discord if (_task != null) { try { DisconnectInternal(new Exception("Disconnect requested by user."), false); } catch (NullReferenceException) { } - try { await _task; } catch (NullReferenceException) { } + try { await _task.ConfigureAwait(false); } catch (NullReferenceException) { } } } @@ -131,90 +129,94 @@ namespace Discord }; } - private async Task ReceiveAsync() + private Task ReceiveAsync() { var cancelSource = _disconnectToken; var cancelToken = cancelSource.Token; - await Task.Yield(); - - var buffer = new byte[ReceiveChunkSize]; - var builder = new StringBuilder(); - try + return Task.Run(async () => { - while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) + var buffer = new byte[ReceiveChunkSize]; + var builder = new StringBuilder(); + + try { - WebSocketReceiveResult result = null; - do + while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) { - if (_webSocket.State != WebSocketState.Open || cancelToken.IsCancellationRequested) - return; - - try + WebSocketReceiveResult result = null; + do { - result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), cancelToken); - } - catch (Win32Exception ex) - when (ex.HResult == HR_TIMEOUT) - { - string msg = $"Connection timed out."; - RaiseOnDebugMessage(DebugMessageType.Connection, msg); - DisconnectInternal(new Exception(msg)); - return; - } + if (_webSocket.State != WebSocketState.Open || cancelToken.IsCancellationRequested) + return; + + try + { + result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) + when (ex.HResult == HR_TIMEOUT) + { + string msg = $"Connection timed out."; + RaiseOnDebugMessage(DebugMessageType.Connection, msg); + DisconnectInternal(new Exception(msg)); + return; + } + + if (result.MessageType == WebSocketMessageType.Close) + { + string msg = $"Got Close Message ({result.CloseStatus?.ToString() ?? "Unexpected"}, {result.CloseStatusDescription ?? "No Reason"})"; + RaiseOnDebugMessage(DebugMessageType.Connection, msg); + DisconnectInternal(new Exception(msg)); + await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); + return; + } + else + builder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count)); - if (result.MessageType == WebSocketMessageType.Close) - { - string msg = $"Got Close Message ({result.CloseStatus?.ToString() ?? "Unexpected"}, {result.CloseStatusDescription ?? "No Reason"})"; - RaiseOnDebugMessage(DebugMessageType.Connection, msg); - DisconnectInternal(new Exception(msg)); - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); - return; } - else - builder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count)); - - } - while (result == null || !result.EndOfMessage); + while (result == null || !result.EndOfMessage); #if DEBUG - System.Diagnostics.Debug.WriteLine(">>> " + builder.ToString()); + System.Diagnostics.Debug.WriteLine(">>> " + builder.ToString()); #endif - await ProcessMessage(builder.ToString()); + await ProcessMessage(builder.ToString()).ConfigureAwait(false); - builder.Clear(); + builder.Clear(); + } } - } - catch (OperationCanceledException) { } - catch (Exception ex) { DisconnectInternal(ex); } - } - private async Task SendAsync() + catch (OperationCanceledException) { } + catch (Exception ex) { DisconnectInternal(ex); } + }); + } + private Task SendAsync() { var cancelSource = _disconnectToken; var cancelToken = cancelSource.Token; - await Task.Yield(); - try + return Task.Run(async () => { - byte[] bytes; - while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) + try { - if (_heartbeatInterval > 0) + byte[] bytes; + while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) { - DateTime now = DateTime.UtcNow; - if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval) + if (_heartbeatInterval > 0) { - await SendMessage(GetKeepAlive(), cancelToken); - _lastHeartbeat = now; + DateTime now = DateTime.UtcNow; + if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval) + { + await SendMessage(GetKeepAlive(), cancelToken).ConfigureAwait(false); + _lastHeartbeat = now; + } } + while (_sendQueue.TryDequeue(out bytes)) + await SendMessage(bytes, cancelToken).ConfigureAwait(false); + await Task.Delay(_sendInterval, cancelToken).ConfigureAwait(false); } - while (_sendQueue.TryDequeue(out bytes)) - await SendMessage(bytes, cancelToken); - await Task.Delay(_sendInterval, cancelToken); } - } - catch (OperationCanceledException) { } - catch (Exception ex) { DisconnectInternal(ex); } + catch (OperationCanceledException) { } + catch (Exception ex) { DisconnectInternal(ex); } + }); } protected abstract Task ProcessMessage(string json); @@ -252,7 +254,7 @@ namespace Discord try { - await _webSocket.SendAsync(new ArraySegment(message, offset, count), WebSocketMessageType.Text, isLast, cancelToken); + await _webSocket.SendAsync(new ArraySegment(message, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); } catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) diff --git a/src/Discord.Net/Helpers/Extensions.cs b/src/Discord.Net/Helpers/Extensions.cs index fe1bf2a44..cdbe47284 100644 --- a/src/Discord.Net/Helpers/Extensions.cs +++ b/src/Discord.Net/Helpers/Extensions.cs @@ -9,12 +9,12 @@ namespace Discord.Helpers public static async Task Wait(this CancellationTokenSource tokenSource) { var token = tokenSource.Token; - try { await Task.Delay(-1, token); } + try { await Task.Delay(-1, token).ConfigureAwait(false); } catch (OperationCanceledException) { } } public static async Task Wait(this CancellationToken token) { - try { await Task.Delay(-1, token); } + try { await Task.Delay(-1, token).ConfigureAwait(false); } catch (OperationCanceledException) { } } } diff --git a/src/Discord.Net/Helpers/JsonHttpClient.cs b/src/Discord.Net/Helpers/JsonHttpClient.cs index 093e7f9b0..992963754 100644 --- a/src/Discord.Net/Helpers/JsonHttpClient.cs +++ b/src/Discord.Net/Helpers/JsonHttpClient.cs @@ -117,7 +117,7 @@ namespace Discord.Helpers private async Task Send(HttpMethod method, string path, HttpContent content) where ResponseT : class { - string responseJson = await SendRequest(method, path, content, true); + string responseJson = await SendRequest(method, path, content, true).ConfigureAwait(false); #if TEST_RESPONSES if (path.StartsWith(Endpoints.BaseApi)) return JsonConvert.DeserializeObject(responseJson, _settings); @@ -127,7 +127,7 @@ namespace Discord.Helpers #if TEST_RESPONSES private async Task Send(HttpMethod method, string path, HttpContent content) { - string responseJson = await SendRequest(method, path, content, true); + string responseJson = await SendRequest(method, path, content, true).ConfigureAwait(false); if (path.StartsWith(Endpoints.BaseApi) && !string.IsNullOrEmpty(responseJson)) throw new Exception("API check failed: Response is not empty."); return responseJson; @@ -146,12 +146,12 @@ namespace Discord.Helpers { if (content is StringContent) { - string json = await (content as StringContent).ReadAsStringAsync(); + string json = await (content as StringContent).ReadAsStringAsync().ConfigureAwait(false); RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {json}"); } else { - byte[] bytes = await content.ReadAsByteArrayAsync(); + byte[] bytes = await content.ReadAsByteArrayAsync().ConfigureAwait(false); RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {bytes.Length} bytes"); } } @@ -167,14 +167,14 @@ namespace Discord.Helpers HttpResponseMessage response; if (hasResponse) { - response = await _client.SendAsync(msg, HttpCompletionOption.ResponseContentRead); + response = await _client.SendAsync(msg, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); if (!response.IsSuccessStatusCode) throw new HttpException(response.StatusCode); - result = await response.Content.ReadAsStringAsync(); + result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); } else { - response = await _client.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead); + response = await _client.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); if (!response.IsSuccessStatusCode) throw new HttpException(response.StatusCode); result = null;