| @@ -8,7 +8,7 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Represents a generic Discord client. | /// Represents a generic Discord client. | ||||
| /// </summary> | /// </summary> | ||||
| public interface IDiscordClient : IDisposable | |||||
| public interface IDiscordClient : IDisposable, IAsyncDisposable | |||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the current state of connection. | /// Gets the current state of connection. | ||||
| @@ -145,9 +145,24 @@ namespace Discord.Rest | |||||
| _isDisposed = true; | _isDisposed = true; | ||||
| } | } | ||||
| } | } | ||||
| internal virtual async ValueTask DisposeAsync(bool disposing) | |||||
| { | |||||
| if (!_isDisposed) | |||||
| { | |||||
| #pragma warning disable IDISP007 | |||||
| await ApiClient.DisposeAsync().ConfigureAwait(false); | |||||
| #pragma warning restore IDISP007 | |||||
| _stateLock?.Dispose(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public void Dispose() => Dispose(true); | public void Dispose() => Dispose(true); | ||||
| public ValueTask DisposeAsync() => DisposeAsync(true); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task<int> GetRecommendedShardCountAsync(RequestOptions options = null) | public Task<int> GetRecommendedShardCountAsync(RequestOptions options = null) | ||||
| => ClientHelper.GetRecommendShardCountAsync(this, options); | => ClientHelper.GetRecommendShardCountAsync(this, options); | ||||
| @@ -22,7 +22,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.API | namespace Discord.API | ||||
| { | { | ||||
| internal class DiscordRestApiClient : IDisposable | |||||
| internal class DiscordRestApiClient : IDisposable, IAsyncDisposable | |||||
| { | { | ||||
| private static readonly ConcurrentDictionary<string, Func<BucketIds, string>> _bucketIdGenerators = new ConcurrentDictionary<string, Func<BucketIds, string>>(); | private static readonly ConcurrentDictionary<string, Func<BucketIds, string>> _bucketIdGenerators = new ConcurrentDictionary<string, Func<BucketIds, string>>(); | ||||
| @@ -106,8 +106,29 @@ namespace Discord.API | |||||
| _isDisposed = true; | _isDisposed = true; | ||||
| } | } | ||||
| } | } | ||||
| internal virtual async ValueTask DisposeAsync(bool disposing) | |||||
| { | |||||
| if (!_isDisposed) | |||||
| { | |||||
| if (disposing) | |||||
| { | |||||
| _loginCancelToken?.Dispose(); | |||||
| RestClient?.Dispose(); | |||||
| if (!(RequestQueue is null)) | |||||
| await RequestQueue.DisposeAsync().ConfigureAwait(false); | |||||
| _stateLock?.Dispose(); | |||||
| } | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| public void Dispose() => Dispose(true); | public void Dispose() => Dispose(true); | ||||
| public ValueTask DisposeAsync() => DisposeAsync(true); | |||||
| public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null) | public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null) | ||||
| { | { | ||||
| await _stateLock.WaitAsync().ConfigureAwait(false); | await _stateLock.WaitAsync().ConfigureAwait(false); | ||||
| @@ -41,6 +41,14 @@ namespace Discord.Rest | |||||
| base.Dispose(disposing); | base.Dispose(disposing); | ||||
| } | } | ||||
| internal override async ValueTask DisposeAsync(bool disposing) | |||||
| { | |||||
| if (disposing) | |||||
| await ApiClient.DisposeAsync().ConfigureAwait(false); | |||||
| base.Dispose(disposing); | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| internal override async Task OnLoginAsync(TokenType tokenType, string token) | internal override async Task OnLoginAsync(TokenType tokenType, string token) | ||||
| { | { | ||||
| @@ -1,3 +1,4 @@ | |||||
| using Newtonsoft.Json.Bson; | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| #if DEBUG_LIMITS | #if DEBUG_LIMITS | ||||
| @@ -10,7 +11,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Net.Queue | namespace Discord.Net.Queue | ||||
| { | { | ||||
| internal class RequestQueue : IDisposable | |||||
| internal class RequestQueue : IDisposable, IAsyncDisposable | |||||
| { | { | ||||
| public event Func<string, RateLimitInfo?, Task> RateLimitTriggered; | public event Func<string, RateLimitInfo?, Task> RateLimitTriggered; | ||||
| @@ -143,12 +144,25 @@ namespace Discord.Net.Queue | |||||
| if (!(_cancelTokenSource is null)) | if (!(_cancelTokenSource is null)) | ||||
| { | { | ||||
| _cancelTokenSource.Cancel(); | _cancelTokenSource.Cancel(); | ||||
| _cancelTokenSource?.Dispose(); | |||||
| _cancelTokenSource.Dispose(); | |||||
| _cleanupTask.GetAwaiter().GetResult(); | _cleanupTask.GetAwaiter().GetResult(); | ||||
| } | } | ||||
| _tokenLock?.Dispose(); | _tokenLock?.Dispose(); | ||||
| _clearToken?.Dispose(); | _clearToken?.Dispose(); | ||||
| _requestCancelTokenSource?.Dispose(); | _requestCancelTokenSource?.Dispose(); | ||||
| } | } | ||||
| public async ValueTask DisposeAsync() | |||||
| { | |||||
| if (!(_cancelTokenSource is null)) | |||||
| { | |||||
| _cancelTokenSource.Cancel(); | |||||
| _cancelTokenSource.Dispose(); | |||||
| await _cleanupTask.ConfigureAwait(false); | |||||
| } | |||||
| _tokenLock?.Dispose(); | |||||
| _clearToken?.Dispose(); | |||||
| _requestCancelTokenSource?.Dispose(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -402,5 +402,25 @@ namespace Discord.WebSocket | |||||
| base.Dispose(disposing); | base.Dispose(disposing); | ||||
| } | } | ||||
| internal override ValueTask DisposeAsync(bool disposing) | |||||
| { | |||||
| if (!_isDisposed) | |||||
| { | |||||
| if (disposing) | |||||
| { | |||||
| if (_shards != null) | |||||
| { | |||||
| foreach (var client in _shards) | |||||
| client?.Dispose(); | |||||
| } | |||||
| _connectionGroupLock?.Dispose(); | |||||
| } | |||||
| _isDisposed = true; | |||||
| } | |||||
| return base.DisposeAsync(disposing); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -114,6 +114,35 @@ namespace Discord.API | |||||
| base.Dispose(disposing); | base.Dispose(disposing); | ||||
| } | } | ||||
| #if NETSTANDARD2_1 | |||||
| internal override async ValueTask DisposeAsync(bool disposing) | |||||
| #else | |||||
| internal override ValueTask DisposeAsync(bool disposing) | |||||
| #endif | |||||
| { | |||||
| if (!_isDisposed) | |||||
| { | |||||
| if (disposing) | |||||
| { | |||||
| _connectCancelToken?.Dispose(); | |||||
| (WebSocketClient as IDisposable)?.Dispose(); | |||||
| #if NETSTANDARD2_1 | |||||
| if (!(_decompressor is null)) | |||||
| await _decompressor.DisposeAsync().ConfigureAwait(false); | |||||
| #else | |||||
| _decompressor?.Dispose(); | |||||
| #endif | |||||
| } | |||||
| _isDisposed = true; | |||||
| } | |||||
| #if NETSTANDARD2_1 | |||||
| await base.DisposeAsync(disposing).ConfigureAwait(false); | |||||
| #else | |||||
| return base.DisposeAsync(disposing); | |||||
| #endif | |||||
| } | |||||
| public async Task ConnectAsync() | public async Task ConnectAsync() | ||||
| { | { | ||||
| await _stateLock.WaitAsync().ConfigureAwait(false); | await _stateLock.WaitAsync().ConfigureAwait(false); | ||||
| @@ -197,6 +197,25 @@ namespace Discord.WebSocket | |||||
| base.Dispose(disposing); | base.Dispose(disposing); | ||||
| } | } | ||||
| internal override async ValueTask DisposeAsync(bool disposing) | |||||
| { | |||||
| if (!_isDisposed) | |||||
| { | |||||
| if (disposing) | |||||
| { | |||||
| await StopAsync().ConfigureAwait(false); | |||||
| if (!(ApiClient is null)) | |||||
| await ApiClient.DisposeAsync().ConfigureAwait(false); | |||||
| _stateLock?.Dispose(); | |||||
| } | |||||
| _isDisposed = true; | |||||
| } | |||||
| await base.DisposeAsync(disposing).ConfigureAwait(false); | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| internal override async Task OnLoginAsync(TokenType tokenType, string token) | internal override async Task OnLoginAsync(TokenType tokenType, string token) | ||||
| { | { | ||||