diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 57b2d84ea..4a999092b 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -23,12 +23,12 @@ namespace Discord private readonly JsonSerializer _serializer; private readonly Regex _userRegex, _channelRegex; private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; - private readonly ManualResetEventSlim _blockEvent; + private readonly object _disconnectEvent; private readonly Random _rand; private readonly ConcurrentQueue _pendingMessages; private readonly DiscordClientConfig _config; - private volatile Task _mainTask; + private volatile Task _runTask; protected volatile string _myId, _sessionId; /// Returns the User object for the current logged in user. @@ -37,7 +37,7 @@ namespace Discord /// Returns true if the user has successfully logged in and the websocket connection has been established. public bool IsConnected => _isConnected; - private bool _isConnected; + private bool _isConnected, _isDisconnecting; /// Returns true if this client was requested to disconnect. public bool IsClosing => _disconnectToken.IsCancellationRequested; @@ -56,7 +56,7 @@ namespace Discord /// Initializes a new instance of the DiscordClient class. public DiscordClient(DiscordClientConfig config = null) { - _blockEvent = new ManualResetEventSlim(false); + _disconnectEvent = new object(); _config = config ?? new DiscordClientConfig(); _isDebugMode = _config.EnableDebug; _rand = new Random(); @@ -521,7 +521,6 @@ namespace Discord private async Task ConnectInternal(string token) { - _blockEvent.Reset(); _http.Token = token; string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; if (_isDebugMode) @@ -530,45 +529,62 @@ namespace Discord await _webSocket.ConnectAsync(url).ConfigureAwait(false); await _webSocket.Login(token).ConfigureAwait(false); - _disconnectToken = new CancellationTokenSource(); - if (_config.UseMessageQueue) - _mainTask = MessageQueueLoop(); - else - _mainTask = _disconnectToken.Wait(); - _mainTask = _mainTask.ContinueWith(async x => - { - await _webSocket.DisconnectAsync().ConfigureAwait(false); -#if !DNXCORE50 - if (_config.EnableVoice) - await _voiceWebSocket.DisconnectAsync().ConfigureAwait(false); -#endif - - //Clear send queue - Message ignored; - while (_pendingMessages.TryDequeue(out ignored)) { } - - _channels.Clear(); - _messages.Clear(); - _roles.Clear(); - _servers.Clear(); - _users.Clear(); - - _blockEvent.Set(); - _mainTask = null; - }).Unwrap(); - _isConnected = true; + _runTask = Run(); return token; } /// Disconnects from the Discord server, canceling any pending requests. public async Task Disconnect() { - if (_mainTask != null) + Task task = _runTask; + if (task != null) { try { _disconnectToken.Cancel(); } catch (NullReferenceException) { } - try { await _mainTask.ConfigureAwait(false); } catch (NullReferenceException) { } + try { await task.ConfigureAwait(false); } catch (NullReferenceException) { } } } + private async Task Run() + { + _disconnectToken = new CancellationTokenSource(); + + //Run Loops + Task task; + if (_config.UseMessageQueue) + task = MessageQueueLoop(); + else + task = _disconnectToken.Wait(); + + _isConnected = true; + + await task.ConfigureAwait(false); + await Cleanup(); + } + //TODO: What happens if a reconnect occurs and caches havent been cleared yet? Compare to DiscordWebSocket.Cleanup() + private async Task Cleanup() + { + _isDisconnecting = true; + + await _webSocket.DisconnectAsync().ConfigureAwait(false); +#if !DNXCORE50 + if (_config.EnableVoice) + await _voiceWebSocket.DisconnectAsync().ConfigureAwait(false); +#endif + + Message ignored; + while (_pendingMessages.TryDequeue(out ignored)) { } + + _channels.Clear(); + _messages.Clear(); + _roles.Clear(); + _servers.Clear(); + _users.Clear(); + + _runTask = null; + _isConnected = false; + _isDisconnecting = false; + Monitor.Pulse(_disconnectEvent); + } + //Voice public Task JoinVoiceServer(string channelId) => JoinVoiceServer(_channels[channelId]); @@ -647,7 +663,7 @@ namespace Discord //Helpers private void CheckReady() { - if (_blockEvent.IsSet) + if (_isDisconnecting) throw new InvalidOperationException("The client is currently disconnecting."); else if (!_isConnected) throw new InvalidOperationException("The client is not currently connected to Discord"); @@ -669,7 +685,8 @@ namespace Discord /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. public void Block() { - _blockEvent.Wait(); + while (_isConnected) + Monitor.Wait(_disconnectEvent, TimeSpan.FromSeconds(3)); } } } diff --git a/src/Discord.Net/DiscordVoiceSocket.cs b/src/Discord.Net/DiscordVoiceSocket.cs index 771abee8e..b1b95bcc1 100644 --- a/src/Discord.Net/DiscordVoiceSocket.cs +++ b/src/Discord.Net/DiscordVoiceSocket.cs @@ -129,7 +129,7 @@ namespace Discord await base.BeginConnect().ConfigureAwait(false); var cancelToken = _disconnectToken.Token; - await Task.Factory.StartNew(() => + await Task.Run(() => { try { diff --git a/src/Discord.Net/DiscordWebSocket.cs b/src/Discord.Net/DiscordWebSocket.cs index f659c0fa3..0c577ba13 100644 --- a/src/Discord.Net/DiscordWebSocket.cs +++ b/src/Discord.Net/DiscordWebSocket.cs @@ -27,7 +27,7 @@ namespace Discord protected ExceptionDispatchInfo _disconnectReason; private ClientWebSocket _webSocket; private DateTime _lastHeartbeat; - private Task _task; + private Task _runTask; private bool _isConnected, _wasDisconnectUnexpected; public DiscordWebSocket(DiscordClient client, int timeout, int interval, bool isDebug) @@ -60,44 +60,17 @@ namespace Discord OnConnect(); - _lastHeartbeat = DateTime.UtcNow; - _task = Task.Factory.ContinueWhenAll(CreateTasks(), x => - { - if (_isDebug) - RaiseOnDebugMessage(DebugMessageType.Connection, $"Disconnected."); - - //Do not clean up until all tasks have ended - OnDisconnect(); - - bool wasUnexpected = _wasDisconnectUnexpected; - _disconnectToken.Dispose(); - _disconnectToken = null; - _wasDisconnectUnexpected = false; - - //Clear send queue - _heartbeatInterval = 0; - _lastHeartbeat = DateTime.MinValue; - _webSocket.Dispose(); - _webSocket = null; - byte[] ignored; - while (_sendQueue.TryDequeue(out ignored)) { } - - _task = null; - if (_isConnected) - { - _isConnected = false; - RaiseDisconnected(wasUnexpected); - } - }); + _runTask = Run(); } public Task ReconnectAsync() => ConnectAsync(_host); public async Task DisconnectAsync() { - if (_task != null) + Task task = _runTask; + if (task != null) { try { DisconnectInternal(new Exception("Disconnect requested by user."), false); } catch (NullReferenceException) { } - try { await _task.ConfigureAwait(false); } catch (NullReferenceException) { } + try { await task.ConfigureAwait(false); } catch (NullReferenceException) { } } } @@ -120,6 +93,39 @@ namespace Discord RaiseConnected(); } + private async Task Run() + { + _lastHeartbeat = DateTime.UtcNow; + + await Task.WhenAll(CreateTasks()); + Cleanup(); + } + private void Cleanup() + { + if (_isDebug) + RaiseOnDebugMessage(DebugMessageType.Connection, $"Disconnected."); + OnDisconnect(); + + bool wasUnexpected = _wasDisconnectUnexpected; + _disconnectToken.Dispose(); + _disconnectToken = null; + _wasDisconnectUnexpected = false; + + _heartbeatInterval = 0; + _lastHeartbeat = DateTime.MinValue; + _webSocket.Dispose(); + _webSocket = null; + byte[] ignored; + while (_sendQueue.TryDequeue(out ignored)) { } + + _runTask = null; + if (_isConnected) + { + _isConnected = false; + RaiseDisconnected(wasUnexpected); + } + } + protected virtual Task[] CreateTasks() { return new Task[]