diff --git a/CryptoExchange.Net/Interfaces/ISpotClient.cs b/CryptoExchange.Net/Interfaces/ISpotClient.cs
index a64f33c..6293e49 100644
--- a/CryptoExchange.Net/Interfaces/ISpotClient.cs
+++ b/CryptoExchange.Net/Interfaces/ISpotClient.cs
@@ -2,6 +2,7 @@
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
@@ -28,29 +29,32 @@ namespace CryptoExchange.Net.Interfaces
///
/// Get the symbol name based on a base and quote asset
///
- ///
- ///
+ /// The base asset
+ /// The quote asset
///
string GetSymbolName(string baseAsset, string quoteAsset);
///
/// Get a list of symbols for the exchange
///
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetSymbolsAsync();
+ Task>> GetSymbolsAsync(CancellationToken ct = default);
///
/// Get a ticker for the exchange
///
/// The symbol to get klines for
+ /// [Optional] Cancellation token for cancelling the request
///
- Task> GetTickerAsync(string symbol);
+ Task> GetTickerAsync(string symbol, CancellationToken ct = default);
///
/// Get a list of tickers for the exchange
///
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetTickersAsync();
+ Task>> GetTickersAsync(CancellationToken ct = default);
///
/// Get a list of candles for a given symbol on the exchange
@@ -60,67 +64,76 @@ namespace CryptoExchange.Net.Interfaces
/// [Optional] Start time to retrieve klines for
/// [Optional] End time to retrieve klines for
/// [Optional] Max number of results
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetKlinesAsync(string symbol, TimeSpan timespan, DateTime? startTime = null, DateTime? endTime = null, int? limit = null);
+ Task>> GetKlinesAsync(string symbol, TimeSpan timespan, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default);
///
/// Get the order book for a symbol
///
/// The symbol to get the book for
+ /// [Optional] Cancellation token for cancelling the request
///
- Task> GetOrderBookAsync(string symbol);
+ Task> GetOrderBookAsync(string symbol, CancellationToken ct = default);
///
/// The recent trades for a symbol
///
/// The symbol to get the trades for
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetRecentTradesAsync(string symbol);
+ Task>> GetRecentTradesAsync(string symbol, CancellationToken ct = default);
///
/// Get balances
///
/// [Optional] The account id to retrieve balances for, required for some exchanges, ignored otherwise
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetBalancesAsync(string? accountId = null);
+ Task>> GetBalancesAsync(string? accountId = null, CancellationToken ct = default);
///
/// Get an order by id
///
/// The id
/// [Optional] The symbol the order is on, required for some exchanges, ignored otherwise
+ /// [Optional] Cancellation token for cancelling the request
///
- Task> GetOrderAsync(string orderId, string? symbol = null);
+ Task> GetOrderAsync(string orderId, string? symbol = null, CancellationToken ct = default);
///
/// Get trades for an order by id
///
/// The id
/// [Optional] The symbol the order is on, required for some exchanges, ignored otherwise
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetOrderTradesAsync(string orderId, string? symbol = null);
+ Task>> GetOrderTradesAsync(string orderId, string? symbol = null, CancellationToken ct = default);
///
/// Get a list of open orders
///
/// [Optional] The symbol to get open orders for, required for some exchanges, ignored otherwise
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetOpenOrdersAsync(string? symbol = null);
+ Task>> GetOpenOrdersAsync(string? symbol = null, CancellationToken ct = default);
///
/// Get a list of closed orders
///
/// [Optional] The symbol to get closed orders for, required for some exchanges, ignored otherwise
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetClosedOrdersAsync(string? symbol = null);
+ Task>> GetClosedOrdersAsync(string? symbol = null, CancellationToken ct = default);
///
/// Cancel an order by id
///
/// The id
/// [Optional] The symbol the order is on, required for some exchanges, ignored otherwise
+ /// [Optional] Cancellation token for cancelling the request
///
- Task> CancelOrderAsync(string orderId, string? symbol = null);
+ Task> CancelOrderAsync(string orderId, string? symbol = null, CancellationToken ct = default);
}
///
@@ -138,14 +151,16 @@ namespace CryptoExchange.Net.Interfaces
/// The price of the order, only for limit orders
/// [Optional] The account id to place the order on, required for some exchanges, ignored otherwise
/// [Optional] Leverage for this order. This is needed for some exchanges. For exchanges where this is not needed this parameter is ignored (and should be set before hand)
+ /// [Optional] Cancellation token for cancelling the request
/// The id of the resulting order
- Task> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, int? leverage = null, string? accountId = null);
+ Task> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, int? leverage = null, string? accountId = null, CancellationToken ct = default);
///
/// Get position
///
+ /// [Optional] Cancellation token for cancelling the request
///
- Task>> GetPositionsAsync();
+ Task>> GetPositionsAsync(CancellationToken ct = default);
}
///
@@ -162,7 +177,8 @@ namespace CryptoExchange.Net.Interfaces
/// The quantity of the order
/// The price of the order, only for limit orders
/// [Optional] The account id to place the order on, required for some exchanges, ignored otherwise
+ /// [Optional] Cancellation token for cancelling the request
/// The id of the resulting order
- Task> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, string? accountId = null);
+ Task> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, string? accountId = null, CancellationToken ct = default);
}
}
diff --git a/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs b/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs
index 9cf4f2d..ece5f1d 100644
--- a/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs
+++ b/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.Objects;
@@ -88,8 +89,9 @@ namespace CryptoExchange.Net.Interfaces
///
/// Start connecting and synchronizing the order book
///
+ /// A cancellation token to stop the order book when canceled
///
- Task> StartAsync();
+ Task> StartAsync(CancellationToken? ct = null);
///
/// Stop syncing the order book
diff --git a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs
index 84df703..75a0de2 100644
--- a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs
+++ b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs
@@ -26,6 +26,7 @@ namespace CryptoExchange.Net.OrderBook
private bool _stopProcessing;
private Task? _processTask;
+ private CancellationTokenSource _cts;
private readonly AsyncResetEvent _queueEvent;
private readonly ConcurrentQueue
///
- protected abstract Task> DoStartAsync();
+ protected abstract Task> DoStartAsync(CancellationToken ct);
///
/// Reset the order book
@@ -316,7 +338,7 @@ namespace CryptoExchange.Net.OrderBook
/// Resync the order book
///
///
- protected abstract Task> DoResyncAsync();
+ protected abstract Task> DoResyncAsync(CancellationToken ct);
///
/// Implementation for validating a checksum value with the current order book. If checksum validation fails (returns false)
@@ -459,16 +481,25 @@ namespace CryptoExchange.Net.OrderBook
/// Wait until the order book snapshot has been set
///
/// Max wait time
+ /// Cancellation token
///
- protected async Task> WaitForSetOrderBookAsync(int timeout)
+ protected async Task> WaitForSetOrderBookAsync(int timeout, CancellationToken ct)
{
var startWait = DateTime.UtcNow;
while (!bookSet && Status == OrderBookStatus.Syncing)
{
+ if(ct.IsCancellationRequested)
+ return new CallResult(new CancellationRequestedError());
+
if ((DateTime.UtcNow - startWait).TotalMilliseconds > timeout)
return new CallResult(new ServerError("Timeout while waiting for data"));
- await Task.Delay(10).ConfigureAwait(false);
+ try
+ {
+ await Task.Delay(10, ct).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ { }
}
return new CallResult(true);
@@ -533,7 +564,7 @@ namespace CryptoExchange.Net.OrderBook
if (Status != OrderBookStatus.Syncing)
return;
- var resyncResult = await DoResyncAsync().ConfigureAwait(false);
+ var resyncResult = await DoResyncAsync(_cts.Token).ConfigureAwait(false);
success = resyncResult;
}
@@ -665,6 +696,12 @@ namespace CryptoExchange.Net.OrderBook
Status = OrderBookStatus.Syncing;
_ = Task.Run(async () =>
{
+ if(_subscription == null)
+ {
+ Status = OrderBookStatus.Disconnected;
+ return;
+ }
+
await _subscription!.UnsubscribeAsync().ConfigureAwait(false);
Reset();
_stopProcessing = false;