diff --git a/ArchiSteamFarm/Actions.cs b/ArchiSteamFarm/Actions.cs index e638aa7ae..1f141b627 100644 --- a/ArchiSteamFarm/Actions.cs +++ b/ArchiSteamFarm/Actions.cs @@ -34,7 +34,7 @@ using JetBrains.Annotations; using SteamKit2; namespace ArchiSteamFarm { - public sealed class Actions : IDisposable { + public sealed class Actions : IAsyncDisposable { private static readonly SemaphoreSlim GiftCardsSemaphore = new SemaphoreSlim(1, 1); private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1); @@ -48,12 +48,14 @@ namespace ArchiSteamFarm { internal Actions([JetBrains.Annotations.NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot)); - public void Dispose() { + public async ValueTask DisposeAsync() { // Those are objects that are always being created if constructor doesn't throw exception TradingSemaphore.Dispose(); // Those are objects that might be null and the check should be in-place - CardsFarmerResumeTimer?.Dispose(); + if (CardsFarmerResumeTimer != null) { + await CardsFarmerResumeTimer.DisposeAsync().ConfigureAwait(false); + } } [PublicAPI] diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index a35840c2e..4e9b0fed5 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -41,7 +41,7 @@ using SteamKit2; using SteamKit2.Unified.Internal; namespace ArchiSteamFarm { - public sealed class Bot : IDisposable { + public sealed class Bot : IAsyncDisposable { internal const ushort CallbackSleep = 500; // In milliseconds internal const ushort MaxMessagePrefixLength = MaxMessageLength - ReservedMessageLength - 2; // 2 for a minimum of 2 characters (escape one and real one) internal const byte MinPlayingBlockedTTL = 60; // Delay in seconds added when account was occupied during our disconnect, to not disconnect other Steam client session too soon @@ -282,27 +282,46 @@ namespace ArchiSteamFarm { ); } - public void Dispose() { + public async ValueTask DisposeAsync() { // Those are objects that are always being created if constructor doesn't throw exception - Actions.Dispose(); + ArchiWebHandler.Dispose(); CallbackSemaphore.Dispose(); GamesRedeemerInBackgroundSemaphore.Dispose(); InitializationSemaphore.Dispose(); MessagingSemaphore.Dispose(); PICSSemaphore.Dispose(); + Trading.Dispose(); + + await Actions.DisposeAsync().ConfigureAwait(false); + await CardsFarmer.DisposeAsync().ConfigureAwait(false); + await HeartBeatTimer.DisposeAsync().ConfigureAwait(false); // Those are objects that might be null and the check should be in-place - ArchiWebHandler?.Dispose(); BotDatabase?.Dispose(); - CardsFarmer?.Dispose(); - ConnectionFailureTimer?.Dispose(); - GamesRedeemerInBackgroundTimer?.Dispose(); - HeartBeatTimer?.Dispose(); - PlayingWasBlockedTimer?.Dispose(); - SendItemsTimer?.Dispose(); - Statistics?.Dispose(); - SteamSaleEvent?.Dispose(); - Trading?.Dispose(); + + if (ConnectionFailureTimer != null) { + await ConnectionFailureTimer.DisposeAsync().ConfigureAwait(false); + } + + if (GamesRedeemerInBackgroundTimer != null) { + await GamesRedeemerInBackgroundTimer.DisposeAsync().ConfigureAwait(false); + } + + if (PlayingWasBlockedTimer != null) { + await PlayingWasBlockedTimer.DisposeAsync().ConfigureAwait(false); + } + + if (SendItemsTimer != null) { + await SendItemsTimer.DisposeAsync().ConfigureAwait(false); + } + + if (Statistics != null) { + await Statistics.DisposeAsync().ConfigureAwait(false); + } + + if (SteamSaleEvent != null) { + await SteamSaleEvent.DisposeAsync().ConfigureAwait(false); + } } [PublicAPI] @@ -1137,7 +1156,8 @@ namespace ArchiSteamFarm { if (!Bots.TryAdd(botName, bot)) { ASF.ArchiLogger.LogNullError(nameof(bot)); - bot.Dispose(); + + await bot.DisposeAsync().ConfigureAwait(false); return; } @@ -1817,7 +1837,8 @@ namespace ArchiSteamFarm { } if (SteamSaleEvent != null) { - SteamSaleEvent.Dispose(); + await SteamSaleEvent.DisposeAsync().ConfigureAwait(false); + SteamSaleEvent = null; } diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 8175f9915..aa4b9e028 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -39,7 +39,7 @@ using Newtonsoft.Json; using SteamKit2; namespace ArchiSteamFarm { - public sealed class CardsFarmer : IDisposable { + public sealed class CardsFarmer : IAsyncDisposable { internal const byte DaysForRefund = 14; // In how many days since payment we're allowed to refund internal const byte HoursForRefund = 2; // Up to how many hours we're allowed to play for refund @@ -111,14 +111,16 @@ namespace ArchiSteamFarm { } } - public void Dispose() { + public async ValueTask DisposeAsync() { // Those are objects that are always being created if constructor doesn't throw exception EventSemaphore.Dispose(); FarmingInitializationSemaphore.Dispose(); FarmingResetSemaphore.Dispose(); // Those are objects that might be null and the check should be in-place - IdleFarmingTimer?.Dispose(); + if (IdleFarmingTimer != null) { + await IdleFarmingTimer.DisposeAsync().ConfigureAwait(false); + } } internal void OnDisconnected() { diff --git a/ArchiSteamFarm/MobileAuthenticator.cs b/ArchiSteamFarm/MobileAuthenticator.cs index 4e4ea14e1..79de7c09b 100644 --- a/ArchiSteamFarm/MobileAuthenticator.cs +++ b/ArchiSteamFarm/MobileAuthenticator.cs @@ -349,11 +349,9 @@ namespace ArchiSteamFarm { Array.Copy(Encoding.UTF8.GetBytes(tag), 0, buffer, 8, bufferSize - 8); } - byte[] hash; + using HMACSHA1 hmac = new HMACSHA1(identitySecret); - using (HMACSHA1 hmac = new HMACSHA1(identitySecret)) { - hash = hmac.ComputeHash(buffer); - } + byte[] hash = hmac.ComputeHash(buffer); return Convert.ToBase64String(hash); } diff --git a/ArchiSteamFarm/RuntimeCompatibility.cs b/ArchiSteamFarm/RuntimeCompatibility.cs index 93a8073fb..ae8e7a0e8 100644 --- a/ArchiSteamFarm/RuntimeCompatibility.cs +++ b/ArchiSteamFarm/RuntimeCompatibility.cs @@ -127,16 +127,16 @@ namespace ArchiSteamFarm { return builder; } + // ReSharper disable once UseDeconstructionOnParameter - we actually implement deconstruction here public static void Deconstruct(this KeyValuePair kv, out TKey key, out TValue value) { key = kv.Key; value = kv.Value; } - [NotNull] - public static Task DisposeAsync([NotNull] this IDisposable disposable) { + public static ValueTask DisposeAsync([NotNull] this IDisposable disposable) { disposable.Dispose(); - return Task.CompletedTask; + return default; } public static async Task ReceiveAsync([NotNull] this WebSocket webSocket, [NotNull] byte[] buffer, CancellationToken cancellationToken) => await webSocket.ReceiveAsync(new ArraySegment(buffer), cancellationToken).ConfigureAwait(false); diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index 104a88c20..81721a89b 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -33,7 +33,7 @@ using JetBrains.Annotations; using Newtonsoft.Json; namespace ArchiSteamFarm { - internal sealed class Statistics : IDisposable { + internal sealed class Statistics : IAsyncDisposable { private const byte MaxMatchedBotsHard = 40; // Determines how many bots we can attempt to match in total, where match attempt is equal to analyzing bot's inventory private const byte MaxMatchedBotsSoft = MaxMatchedBotsHard / 2; // Determines how many consecutive empty matches we need to get before we decide to skip bots from the same category private const byte MaxMatchingRounds = 10; // Determines maximum amount of matching rounds we're going to consider before leaving the rest of work for the next batch @@ -71,10 +71,11 @@ namespace ArchiSteamFarm { ); } - public void Dispose() { + public async ValueTask DisposeAsync() { MatchActivelySemaphore.Dispose(); - MatchActivelyTimer.Dispose(); RequestsSemaphore.Dispose(); + + await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false); } internal async Task OnHeartBeat() { diff --git a/ArchiSteamFarm/SteamSaleEvent.cs b/ArchiSteamFarm/SteamSaleEvent.cs index ab94069f0..d95f6e967 100644 --- a/ArchiSteamFarm/SteamSaleEvent.cs +++ b/ArchiSteamFarm/SteamSaleEvent.cs @@ -28,7 +28,7 @@ using ArchiSteamFarm.Localization; using JetBrains.Annotations; namespace ArchiSteamFarm { - internal sealed class SteamSaleEvent : IDisposable { + internal sealed class SteamSaleEvent : IAsyncDisposable { private const byte MaxSingleQueuesDaily = 3; // This is only a failsafe for infinite queue clearing private readonly Bot Bot; @@ -45,7 +45,7 @@ namespace ArchiSteamFarm { ); } - public void Dispose() => SaleEventTimer.Dispose(); + public async ValueTask DisposeAsync() => await SaleEventTimer.DisposeAsync().ConfigureAwait(false); private async Task ExploreDiscoveryQueue() { if (!Bot.IsConnectedAndLoggedOn) { diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index 34aafb9e2..557fad14d 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -114,7 +114,7 @@ namespace ArchiSteamFarm { HtmlDocumentResponse result = null; for (int i = 0; i < maxTries; i++) { - using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); + await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); if (response == null) { return null; @@ -163,7 +163,7 @@ namespace ArchiSteamFarm { ObjectResponse result = null; for (byte i = 0; i < maxTries; i++) { - using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); + await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); // ReSharper disable once UseNullPropagationWhenPossible - false check if (response == null) { @@ -219,7 +219,7 @@ namespace ArchiSteamFarm { XmlDocumentResponse result = null; for (byte i = 0; i < maxTries; i++) { - using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); + await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); // ReSharper disable once UseNullPropagationWhenPossible - false check if (response == null) { @@ -345,7 +345,7 @@ namespace ArchiSteamFarm { HtmlDocumentResponse result = null; for (int i = 0; i < maxTries; i++) { - using StreamResponse response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); + await using StreamResponse response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); if (response == null) { return null; @@ -394,7 +394,7 @@ namespace ArchiSteamFarm { ObjectResponse result = null; for (byte i = 0; i < maxTries; i++) { - using StreamResponse response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); + await using StreamResponse response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); if (response == null) { return null; @@ -479,7 +479,7 @@ namespace ArchiSteamFarm { const byte printPercentage = 10; const byte maxBatches = 99 / printPercentage; - using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); + await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false); if (response == null) { continue; @@ -915,7 +915,7 @@ namespace ArchiSteamFarm { } } - internal sealed class StreamResponse : BasicResponse, IDisposable { + internal sealed class StreamResponse : BasicResponse, IAsyncDisposable { internal readonly Stream Content; internal readonly uint Length; @@ -938,8 +938,10 @@ namespace ArchiSteamFarm { ResponseMessage = httpResponseMessage; } - public void Dispose() { - Content?.Dispose(); + public async ValueTask DisposeAsync() { + if (Content != null) { + await Content.DisposeAsync().ConfigureAwait(false); + } ResponseMessage.Dispose(); }