/// <summary> /// SQLを実行します。 /// </summary> /// <typeparam name="T">戻り値</typeparam> /// <param name="execute">実行内容を表すアクション</param> /// <param name="sql">SQL文</param> /// <param name="parameters">SQLパラメータ</param> /// <returns></returns> private async Task <T> ExecuteAsync <T>(Func <Task <T> > execute, string sql, DbParameter[] parameters) { var sp = new Stopwatch(); sp.Start(); try { await SemaphoreManager.WaitAsync(GetLockKey()); command.CommandText = sql; command.Parameters.Clear(); command.Parameters.AddRange(parameters); return(await execute()); } catch { // エラーが発生したらコンソールに表示 var sb = new StringBuilder(); sb.AppendLine($"{sp.Elapsed} ******************************************************************"); sb.AppendLine(sql.ToString()); sb.AppendLine(parameters.Select(p => p.Value.ToString()).GetString(",")); ServiceFactory.MessageService.Debug(sb.ToString()); throw; } finally { if (1000 < sp.ElapsedMilliseconds) { // 1秒超えたらコンソールに表示 var sb = new StringBuilder(); sb.AppendLine($"{sp.Elapsed} ******************************************************************"); sb.AppendLine(sql.ToString()); sb.AppendLine(parameters.Select(p => p.Value.ToString()).GetString(",")); ServiceFactory.MessageService.Debug(sb.ToString()); } SemaphoreManager.Release(GetLockKey()); } }
/// <summary> /// 実行完了を他スレッドに通知します。 /// </summary> internal void Release() { SemaphoreManager.Release(GetLockKey()); }
private async ValueTask HandleEvents(IReadOnlyList <ExBalanceUpdateMessage> messages) { using var activity = MyTelemetry.StartActivity("Handle ExBalanceUpdateMessage's") ?.AddTag("count-events", messages.Count); try { var sw = new Stopwatch(); sw.Start(); var list = new List <AvgPriceUpdate>(); var calculatedList = new List <AvgPriceUpdate>(); if (!messages.Any()) { return; } list.AddRange(GetOperationUpdates(messages)); if (!list.Any()) { return; } await using var ctx = DatabaseContext.Create(_dbContextOptionsBuilder); var groups = list.GroupBy(t => new { t.AssetId, t.WalletId }); foreach (var group in groups) { var semaphore = SemaphoreManager.GetOrCreate(group.Key.WalletId, group.Key.AssetId); await semaphore.WaitAsync(); try { var groupedList = group.OrderBy(t => t.TimeStamp).ToList(); var snapshot = await _snapshotManager.GetSnapshot(group.Key.WalletId, group.Key.AssetId); if (snapshot != null) { groupedList = groupedList.Where(t => t.TimeStamp > snapshot.LastTimestamp).ToList(); } if (!groupedList.Any()) { continue; } groupedList = AvgPriceCalculator.CalculateAvgPrice(groupedList, snapshot); calculatedList.AddRange(groupedList); var openTimestamp = groupedList.LastOrDefault(t => t.Balance == 0)?.TimeStamp ?? snapshot?.OpenTimestamp ?? default; var lastUpdate = groupedList.Last(); var newSnapshot = new SnapshotEntity { WalletId = lastUpdate.WalletId, AssetId = lastUpdate.AssetId, Balance = lastUpdate.Balance, AvgOpenPrice = lastUpdate.AvgOpenPrice, OpenTimestamp = openTimestamp, LastTimestamp = lastUpdate.TimeStamp }; await _snapshotManager.UpsertSnapshots(new() { newSnapshot }); } finally { semaphore.Release(); } } await ctx.UpsertAvgPriceAsync(calculatedList.Where(t => t.EventType != "Refund Blockchain Withdrawal").Select(update => new OperationHistoryEntity(update))); await ctx.SaveChangesAsync(); sw.Stop(); _logger.LogDebug( "Write {countUpdates} avg price updates in database from {countEvents} ME events. Time: {elapsedText} [{elapsedMilliseconds} ms]", list.Count, messages.Count, sw.Elapsed.ToString(), sw.ElapsedMilliseconds); } catch (Exception ex) { ex.FailActivity(); messages.AddToActivityAsJsonTag("me events"); throw; } }
/// <summary> /// 同期実行が必要なSQL実行を待機します。 /// </summary> /// <returns></returns> internal async Task WaitAsync() { await SemaphoreManager.WaitAsync(GetLockKey()); }