Smartcoin history refresh optimization
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
@@ -361,20 +361,9 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
_rootlist.Clear();
break;
}
- RefreshCoinsHistories();
});
}
- private void RefreshCoinsHistories()
- {
- foreach (var coin in Coins)
- {
-#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
- coin.RefreshHistoryAsync();
-#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
- }
- }
-
private void SetSelections()
{
SelectAllCheckBoxState = GetCheckBoxesSelectedState(x => true);

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
@@ -56,6 +56,11 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
this.RaisePropertyChanged(nameof(Unspent));
});
+ model.WhenAnyValue(x => x.History).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
+ {
+ this.RaisePropertyChanged(nameof(History));
+ });
+
this.WhenAnyValue(x => x.Status).Subscribe(_ =>
{
this.RaisePropertyChanged(nameof(ToolTip));
@@ -68,10 +73,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
});
Global.ChaumianClient.StateUpdated += ChaumianClient_StateUpdated;
-
-#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
- RefreshHistoryAsync();
-#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
private void ChaumianClient_StateUpdated(object sender, EventArgs e)
@@ -138,23 +139,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public string InCoinJoin => Model.CoinJoinInProgress ? "Yes" : "No";
- public string History
- {
- get => _history;
- set
- {
- this.RaiseAndSetIfChanged(ref _history, value);
- }
- }
-
- public async Task RefreshHistoryAsync()
- {
- History = await Task.Run(() =>
- {
- return string.Join(", ", Global.WalletService.GetHistory(Model, Enumerable.Empty<SmartCoin>()).Select(x => x.Label).Distinct());
- }
- );
- }
+ public string History => Model.History;
public SmartCoinStatus Status
{

MODIFY
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
@@ -200,7 +200,7 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
await Global.InitializeWalletServiceAsync(keyManager);
});
-
+ Global.WalletService.RefreshCoinsHistoriesAsync();
// Successffully initialized.
IoC.Get<IShell>().RemoveDocument(Owner);
// Open Wallet Explorer tabs

MODIFY
WalletWasabi/Models/SmartCoin.cs
WalletWasabi/Models/SmartCoin.cs
@@ -47,6 +47,7 @@ namespace WalletWasabi.Models
private bool _unspent;
private bool _isBanned;
private int _anonymitySet;
+ private string _history;
#endregion Fields
@@ -277,6 +278,19 @@ namespace WalletWasabi.Models
}
}
+ public string History
+ {
+ get => _history;
+ private set
+ {
+ if (value != _history)
+ {
+ _history = value;
+ OnPropertyChanged(nameof(History));
+ }
+ }
+ }
+
#endregion NonSerializableProperties
#region DependentProperties
@@ -449,6 +463,11 @@ namespace WalletWasabi.Models
public bool HasLabel() => !string.IsNullOrWhiteSpace(Label);
+ public void SetHistory(string history)
+ {
+ History = history;
+ }
+
#endregion Methods
#region EqualityAndComparison

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -142,6 +142,12 @@ namespace WalletWasabi.Services
Synchronizer.NewFilter += IndexDownloader_NewFilterAsync;
Synchronizer.Reorged += IndexDownloader_ReorgedAsync;
MemPool.TransactionReceived += MemPool_TransactionReceived;
+ Coins.CollectionChanged += Coins_CollectionChanged;
+ }
+
+ private void Coins_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ RefreshCoinsHistoriesAsync();
}
private void MemPool_TransactionReceived(object sender, SmartTransaction tx)
@@ -1068,6 +1074,64 @@ namespace WalletWasabi.Services
.Distinct();
}
+ private async Task RefreshHistoryAsync(SemaphoreSlim semaphore, SmartCoin coin)
+ {
+ if (semaphore != null)
+ {
+ await semaphore.WaitAsync();
+ }
+ var result = string.Join(", ", GetHistory(coin, Enumerable.Empty<SmartCoin>()).Select(x => x.Label).Distinct());
+ coin.SetHistory(result);
+ semaphore?.Release();
+ }
+
+ private volatile int _refreshCoinCalls;
+
+ public async void RefreshCoinsHistoriesAsync()
+ {
+ try
+ {
+ if (_refreshCoinCalls == 2) //it is running and scheduled to rerun after finished
+ {
+ return;
+ }
+ if (_refreshCoinCalls == 1) //it is running but now we will rerun if finished
+ {
+ Interlocked.Increment(ref _refreshCoinCalls);
+ return;
+ }
+ if (_refreshCoinCalls == 0) //it is not running so we start the work
+ {
+ Interlocked.Increment(ref _refreshCoinCalls);
+ }
+ var unspentCoins = Coins.Where(c => c.Unspent); //refreshing unspent coins history only
+ if (unspentCoins.Any())
+ {
+ const int simultaneousThread = 2; //threads allowed to run simultaneously in threadpool
+ using (SemaphoreSlim semaphore = new SemaphoreSlim(simultaneousThread))
+ {
+ //https://blogs.msdn.microsoft.com/andrewarnottms/2017/05/11/limiting-concurrency-for-faster-and-more-responsive-apps/
+ IEnumerable<Task> tasks = unspentCoins.Select(c => Task.Run(() => RefreshHistoryAsync(semaphore, c)));
+ await Task.WhenAll(tasks); //await all tasks to finish
+ }
+ }
+ if (_refreshCoinCalls == 2) //scheduled to rerun so we start the work again
+ {
+ Interlocked.Exchange(ref _refreshCoinCalls, 0);
+ RefreshCoinsHistoriesAsync();
+ }
+ if (_refreshCoinCalls == 1) //done with the job
+ {
+ Interlocked.Exchange(ref _refreshCoinCalls, 0);
+ }
+ }
+ catch (Exception ex)
+ {
+ Interlocked.Exchange(ref _refreshCoinCalls, 0);
+ Logger.LogError<WalletService>($"Refreshing coins history failed: {ex}");
+ }
+ }
+
#region IDisposable Support
private volatile bool _disposedValue = false; // To detect redundant calls
@@ -1081,6 +1145,7 @@ namespace WalletWasabi.Services
Synchronizer.NewFilter -= IndexDownloader_NewFilterAsync;
Synchronizer.Reorged -= IndexDownloader_ReorgedAsync;
MemPool.TransactionReceived -= MemPool_TransactionReceived;
+ Coins.CollectionChanged -= Coins_CollectionChanged;
IoHelpers.EnsureContainingDirectoryExists(TransactionsFilePath);
string jsonString = JsonConvert.SerializeObject(TransactionCache, Formatting.Indented);

Smartcoin history refresh optimization