Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async ExchangeAPI init #668

Merged
merged 6 commits into from
Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions examples/ExchangeSharpWinForms/MainForm.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Text;
using System.Windows.Forms;
using ExchangeSharp;
Expand Down Expand Up @@ -28,7 +28,7 @@ private async void FetchTickers()
this.UseWaitCursor = true;
try
{
var api = ExchangeAPI.GetExchangeAPI(cmbExchange.SelectedItem as string);
var api = await ExchangeAPI.GetExchangeAPIAsync(cmbExchange.SelectedItem as string);
var tickers = await api.GetTickersAsync();
StringBuilder b = new StringBuilder();
foreach (var ticker in tickers)
Expand All @@ -52,10 +52,10 @@ public MainForm()
InitializeComponent();
}

protected override void OnShown(EventArgs e)
protected override async void OnShown(EventArgs e)
{
base.OnShown(e);
foreach (var exchange in ExchangeAPI.GetExchangeAPIs())
foreach (var exchange in await ExchangeAPI.GetExchangeAPIsAsync())
{
cmbExchange.Items.Add(exchange.Name);
}
Expand Down
132 changes: 88 additions & 44 deletions src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ public abstract partial class ExchangeAPI : BaseAPI, IExchangeAPI
#region Private methods

private static readonly IReadOnlyCollection<Type> exchangeTypes = typeof(ExchangeAPI).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ExchangeAPI)) && !type.IsAbstract).ToArray();
private static readonly ConcurrentDictionary<Type, IExchangeAPI> apis = new ConcurrentDictionary<Type, IExchangeAPI>();
private static readonly ConcurrentDictionary<Type, Task<IExchangeAPI>> apis = new ConcurrentDictionary<Type, Task<IExchangeAPI>>();
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);

private bool initialized;
private bool disposed;

private static IExchangeAPI InitializeAPI(Type? type, Type? knownType = null)
private static async Task<IExchangeAPI> InitializeAPIAsync(Type? type, Type? knownType = null)
{
if (type is null)
{
Expand All @@ -64,30 +65,23 @@ private static IExchangeAPI InitializeAPI(Type? type, Type? knownType = null)
}

const int retryCount = 3;
Exception? ex = null;


// try up to 3 times to init
for (int i = 1; i <= 3; i++)
for (int i = 1; i <= retryCount; i++)
{
try
{
api.InitializeAsync().Sync();
ex = null;
break;
await api.InitializeAsync();
}
catch (Exception _ex)
catch (Exception)
{
ex = _ex;
if (i != retryCount)
if (i == retryCount)
{
Thread.Sleep(5000);
throw;
}
}
}

if (ex != null)
{
throw ex;
Thread.Sleep(5000);
}
}

return api;
Expand Down Expand Up @@ -365,7 +359,7 @@ public void Dispose()
Cache?.Dispose();

// take out of global api dictionary if disposed and we are the current exchange in the dictionary
if (apis.TryGetValue(GetType(), out IExchangeAPI existing) && this == existing)
if (apis.TryGetValue(GetType(), out Task<IExchangeAPI> existing) && this == existing.Result)
{
apis.TryRemove(GetType(), out _);
}
Expand All @@ -386,9 +380,17 @@ private async Task InitializeAsync()
initialized = true;
}

private static IExchangeAPI CreateExchangeAPI(Type? type)
/// <summary>
/// Create an exchange api, by-passing any cache. Use this method for cases
/// where you need multiple instances of the same exchange, for example
/// multiple credentials.
/// </summary>
/// <typeparam name="T">Type of exchange api to create</typeparam>
/// <returns>Created exchange api</returns>
[Obsolete("Use the async version")]
public static T CreateExchangeAPI<T>() where T : ExchangeAPI
{
return InitializeAPI(type);
return CreateExchangeAPIAsync<T>().Result;
}

/// <summary>
Expand All @@ -398,75 +400,117 @@ private static IExchangeAPI CreateExchangeAPI(Type? type)
/// </summary>
/// <typeparam name="T">Type of exchange api to create</typeparam>
/// <returns>Created exchange api</returns>
public static T CreateExchangeAPI<T>() where T : ExchangeAPI
public static async Task<T> CreateExchangeAPIAsync<T>() where T : ExchangeAPI
{
return (T)CreateExchangeAPI(typeof(T));
return (T)await InitializeAPIAsync(typeof(T));
}

/// <summary>
/// Get a cached exchange API given an exchange name (see ExchangeName class)
/// </summary>
/// <param name="exchangeName">Exchange name. Must match the casing of the ExchangeName class name exactly.</param>
/// <returns>Exchange API or null if not found</returns>
[Obsolete("Use the async version")]
public static IExchangeAPI GetExchangeAPI(string exchangeName)
{
return GetExchangeAPIAsync(exchangeName).Result;
}

/// <summary>
/// Get a cached exchange API given an exchange name (see ExchangeName class)
/// </summary>
/// <param name="exchangeName">Exchange name. Must match the casing of the ExchangeName class name exactly.</param>
/// <returns>Exchange API or null if not found</returns>
public static Task<IExchangeAPI> GetExchangeAPIAsync(string exchangeName)
{
Type type = ExchangeName.GetExchangeType(exchangeName);
return GetExchangeAPI(type);
return GetExchangeAPIAsync(type);
}

/// <summary>
/// Get a cached exchange API given a type
/// </summary>
/// <typeparam name="T">Type of exchange to get</typeparam>
/// <returns>Exchange API or null if not found</returns>
[Obsolete("Use the async version")]
public static IExchangeAPI GetExchangeAPI<T>() where T : ExchangeAPI
{
return GetExchangeAPIAsync<T>().Result;
}

public static Task<IExchangeAPI> GetExchangeAPIAsync<T>() where T : ExchangeAPI
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the example in README.md to this new method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
// note: this method will be slightly slow (milliseconds) the first time it is called due to cache miss and initialization
// subsequent calls with cache hits will be nanoseconds
Type type = typeof(T)!;
return GetExchangeAPI(type);
return GetExchangeAPIAsync(type);
}

/// <summary>
/// Get a cached exchange API given a type
/// </summary>
/// <param name="type">Type of exchange</param>
/// <returns>Exchange API or null if not found</returns>
[Obsolete("Use the async version")]
public static IExchangeAPI GetExchangeAPI(Type type)
{
// note: this method will be slightly slow (milliseconds) the first time it is called due to cache miss and initialization
// subsequent calls with cache hits will be nanoseconds
return apis.GetOrAdd(type, _exchangeName =>
return GetExchangeAPIAsync(type).Result;
}

/// <summary>
/// Get a cached exchange API given a type
/// </summary>
/// <param name="type">Type of exchange</param>
/// <returns>Exchange API or null if not found</returns>
public static async Task<IExchangeAPI> GetExchangeAPIAsync(Type type)
{
if (apis.TryGetValue(type, out var result)) return await result;

await semaphore.WaitAsync();
try
{
// find the api type
Type? foundType = exchangeTypes.FirstOrDefault(t => t == type);
return InitializeAPI(foundType, type);
});
// try again inside semaphore
if (apis.TryGetValue(type, out result)) return await result;

// still not found, initialize it
var foundType = exchangeTypes.FirstOrDefault(t => t == type);
return await (apis[type] = InitializeAPIAsync(foundType, type));
}
finally
{
semaphore.Release();
}
}

/// <summary>
/// Get all cached versions of exchange APIs
/// </summary>
/// <returns>All APIs</returns>
[Obsolete("Use the async version")]
public static IExchangeAPI[] GetExchangeAPIs()
{
foreach (Type type in exchangeTypes)
return GetExchangeAPIsAsync().Result;
}

/// <summary>
/// Get all cached versions of exchange APIs
/// </summary>
/// <returns>All APIs</returns>
public static async Task<IExchangeAPI[]> GetExchangeAPIsAsync()
{
var apiList = new List<IExchangeAPI>();
foreach (var kv in apis.ToArray())
{
List<IExchangeAPI> apiList = new List<IExchangeAPI>();
foreach (var kv in apis.ToArray())
if (kv.Value == null)
{
if (kv.Value == null)
{
apiList.Add(GetExchangeAPI(kv.Key));
}
else
{
apiList.Add(kv.Value);
}
apiList.Add(await GetExchangeAPIAsync(kv.Key));
}
else
{
apiList.Add(await kv.Value);
}
return apiList.ToArray();
}
return apis.Values.ToArray();
return apiList.ToArray();
}

/// <summary>
Expand Down
23 changes: 19 additions & 4 deletions src/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,29 @@ public static BinaryReader OpenLogReader(string basePath)
return new BinaryReader(new System.IO.Compression.GZipStream(File.OpenRead(basePath + ".gz"), System.IO.Compression.CompressionMode.Decompress, false));
}

/// <summary>
/// Begins logging exchanges - writes errors to console. You should block the app using Console.ReadLine.
/// </summary>
/// <param name="path">Path to write files to</param>
/// <param name="intervalSeconds">Interval in seconds in between each log calls for each exchange</param>
/// <param name="terminateAction">Call this when the process is about to exit, like a WM_CLOSE message on Windows.</param>
/// <param name="compress">Whether to compress the log files</param>
/// <param name="exchangeNamesAndSymbols">Exchange names and symbols to log</param>
[Obsolete("Use the async version")]
public static void LogExchanges(string path, float intervalSeconds, out Action terminateAction, bool compress, params string[] exchangeNamesAndSymbols)
{
terminateAction = LogExchangesAsync(path, intervalSeconds, compress, exchangeNamesAndSymbols).Result;
}

/// <summary>
/// Begins logging exchanges - writes errors to console. You should block the app using Console.ReadLine.
/// </summary>
/// <param name="path">Path to write files to</param>
/// <param name="intervalSeconds">Interval in seconds in between each log calls for each exchange</param>
/// <param name="terminateAction">Call this when the process is about to exit, like a WM_CLOSE message on Windows.</param>
/// <param name="compress">Whether to compress the log files</param>
/// <param name="exchangeNamesAndSymbols">Exchange names and symbols to log</param>
public static void LogExchanges(string path, float intervalSeconds, out Action terminateAction, bool compress, params string[] exchangeNamesAndSymbols)
/// <returns">Call this when the process is about to exit, like a WM_CLOSE message on Windows.</returns>
public static async Task<Action> LogExchangesAsync(string path, float intervalSeconds, bool compress, params string[] exchangeNamesAndSymbols)
{
bool terminating = false;
Action terminator = null;
Expand All @@ -212,7 +226,7 @@ public static void LogExchanges(string path, float intervalSeconds, out Action t
List<ExchangeLogger> loggers = new List<ExchangeLogger>();
for (int i = 0; i < exchangeNamesAndSymbols.Length;)
{
loggers.Add(new ExchangeLogger(ExchangeAPI.GetExchangeAPI(exchangeNamesAndSymbols[i++]), exchangeNamesAndSymbols[i++], intervalSeconds, path, compress));
loggers.Add(new ExchangeLogger(await ExchangeAPI.GetExchangeAPIAsync(exchangeNamesAndSymbols[i++]), exchangeNamesAndSymbols[i++], intervalSeconds, path, compress));
};
foreach (ExchangeLogger logger in loggers)
{
Expand Down Expand Up @@ -244,7 +258,6 @@ public static void LogExchanges(string path, float intervalSeconds, out Action t
loggers.Clear();
}
};
terminateAction = terminator;

// make sure to close properly
Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
Expand All @@ -256,6 +269,8 @@ public static void LogExchanges(string path, float intervalSeconds, out Action t
terminator();
};
Logger.Info("Loggers \"{0}\" started, press ENTER or CTRL-C to terminate.", string.Join(", ", loggers.Select(l => l.API.Name)));

return terminator;
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/ExchangeSharpConsole/ExchangeSharpConsole.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down
6 changes: 3 additions & 3 deletions src/ExchangeSharpConsole/Options/BaseOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ protected void WaitInteractively()
Console.CursorLeft = 0;
}

protected IExchangeAPI GetExchangeInstance(string exchangeName)
protected Task<IExchangeAPI> GetExchangeInstanceAsync(string exchangeName)
{
return ExchangeAPI.GetExchangeAPI(exchangeName);
return ExchangeAPI.GetExchangeAPIAsync(exchangeName);
}

protected async Task RunWebSocket(string exchangeName, Func<IExchangeAPI, Task<IWebSocket>> getWebSocket)
{
using var api = ExchangeAPI.GetExchangeAPI(exchangeName);
using var api = await ExchangeAPI.GetExchangeAPIAsync(exchangeName);

Console.WriteLine("Connecting web socket to {0}...", api.Name);

Expand Down
2 changes: 1 addition & 1 deletion src/ExchangeSharpConsole/Options/BuyOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public override async Task RunCommand()

protected async Task AddOrder(bool isBuyOrder)
{
using var api = GetExchangeInstance(ExchangeName);
using var api = await GetExchangeInstanceAsync(ExchangeName);

var exchangeOrderRequest = GetExchangeOrderRequest(isBuyOrder, api);

Expand Down
2 changes: 1 addition & 1 deletion src/ExchangeSharpConsole/Options/CancelOrderOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class CancelOrderOption : BaseOption,
{
public override async Task RunCommand()
{
using var api = GetExchangeInstance(ExchangeName);
using var api = await GetExchangeInstanceAsync(ExchangeName);

api.LoadAPIKeys(KeyPath);

Expand Down
2 changes: 1 addition & 1 deletion src/ExchangeSharpConsole/Options/CandlesOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class CandlesOption : BaseOption, IOptionPerExchange, IOptionPerMarketSym
{
public override async Task RunCommand()
{
using var api = GetExchangeInstance(ExchangeName);
using var api = await GetExchangeInstanceAsync(ExchangeName);

var candles = await api.GetCandlesAsync(
MarketSymbol,
Expand Down
2 changes: 1 addition & 1 deletion src/ExchangeSharpConsole/Options/CurrenciesOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class CurrenciesOption : BaseOption, IOptionPerExchange

public override async Task RunCommand()
{
using var api = GetExchangeInstance(ExchangeName);
using var api = await GetExchangeInstanceAsync(ExchangeName);

Authenticate(api);

Expand Down
2 changes: 1 addition & 1 deletion src/ExchangeSharpConsole/Options/DepositAddressOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class DepositAddressOption : BaseOption, IOptionPerExchange, IOptionWithC

public override async Task RunCommand()
{
using var api = GetExchangeInstance(ExchangeName);
using var api = await GetExchangeInstanceAsync(ExchangeName);

Authenticate(api);

Expand Down
2 changes: 1 addition & 1 deletion src/ExchangeSharpConsole/Options/ExampleOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ExampleOption : BaseOption, IOptionWithKey
{
public override async Task RunCommand()
{
using var api = ExchangeAPI.GetExchangeAPI(ExchangeName.Kraken);
using var api = await ExchangeAPI.GetExchangeAPIAsync(ExchangeName.Kraken);
var ticker = await api.GetTickerAsync("XXBTZUSD");

Console.WriteLine("On the Kraken exchange, 1 bitcoin is worth {0} USD.", ticker.Bid);
Expand Down
Loading