This is a followup question to the following question:
Volatile IEnlistmentNotification and TransactionScope.AsyncFlowEnabled = true
The approach accepted in the question above works as long as you don't await multiple statements. Let me show an example:
public class SendResourceManager : IEnlistmentNotification
{
private readonly Action onCommit;
public SendResourceManager(Action onCommit)
{
this.onCommit = onCommit;
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment)
{
Debug.WriteLine("Committing");
this.onCommit();
Debug.WriteLine("Committed");
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
public class AsyncTransactionalMessageSender : ISendMessagesAsync
{
private readonly List<Message> sentMessages = new List<Message>();
public IReadOnlyCollection<Message> SentMessages
{
get { return new ReadOnlyCollection<Message>(this.sentMessages); }
}
public async Task SendAsync(Message message)
{
if (Transaction.Current != null)
{
await Transaction.Current.EnlistVolatileAsync(
new SendResourceManager(async () => await this.SendInternal(message)),
EnlistmentOptions.None);
}
else
{
await this.SendInternal(message);
}
}
private async Task SendInternal(Message message)
{
Debug.WriteLine("Sending");
await Task.Delay(1000);
this.sentMessages.Add(message);
Debug.WriteLine("Sent");
}
}
[Test]
public async Task ScopeRollbackAsync_DoesntSend()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
// We do not commit the scope
}
sender.SentMessages.Should().BeEmpty();
}
[Test]
public async Task ScopeCompleteAsync_Sends()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
tx.Complete();
}
sender.SentMessages.Should().HaveCount(3)
.And.Contain(m => m.Value == "First")
.And.Contain(m => m.Value == "Second")
.And.Contain(m => m.Value == "Last");
}
As soon as you introduce a Task.Delay like shown in the example above the generated asynchronous statemachine will never come back and invoke the this.sentMessages.Add(message) and Debug.WriteLine("Sent")
The problem is I currently see now way to properly enlist asynchronous code inside the enlistment notification. Any ideas how to tackle this challenge?
Related
I am trying to read async from HttpContent but my code exists on this method. It used to work but I recently had to switch to a console app because I wanted to make a discord bot. Does anyone know why the code "stops" when I use this in a console app?
I've tried using an async Main as suggested but it does not work
I am using this code:
public class BazaarInfo
{
[JsonProperty("products")]
public List<BazaarProduct> Products { get; set; }
public static async Task<BazaarInfo> BuildAsync()
{
string url = "https://api.hypixel.net/skyblock/bazaar";
using (HttpResponseMessage response = await ApiHelper.GetApiClient("application/json").GetAsync(url))
{
if (response.IsSuccessStatusCode)
{
BazaarInfo output = await response.Content.ReadAsAsync<BazaarInfo>(); //Stops
return output;
}
else
{
return null;
}
}
}
}
I call it from here:
public class Bazaar
{
public Dictionary<string, BazaarProduct> Products { get; set; }
public static async Task<Bazaar> BuildAsync()
{
var output = new Bazaar();
var bazaarInfo = await BazaarInfo.BuildAsync();
output.Products = bazaarInfo.Products.ToDictionary(product => product.Name);
return output;
}
}
And this:
[Command("bazaar")]
public async Task BuildBzItem ([Remainder]string id)
{
var bazaar = await Bazaar.BuildAsync();
string sellSummary = "";
foreach (var summary in bazaar.Products[id].SellSummary)
sellSummary += summary.Amount + summary.Price;
var builder = new Discord.EmbedBuilder()
{
Description = sellSummary
};
await ReplyAsync("", false, builder.Build());
}
And then here with a discord chat event:
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
var context = new SocketCommandContext(Client, message);
if (message.Author.IsBot) return;
int argPos = 0;
if(message.HasStringPrefix("!", ref argPos))
{
var result = await Commands.ExecuteAsync(context, argPos, Services);
if (!result.IsSuccess) Console.Write(result.ErrorReason);
}
}
And this event is assigned here:
public static async Task Main(string[] args) => await new Program().RunBotAsync();
private DiscordSocketClient Client { get; set; }
private CommandService Commands { get; set; }
private IServiceProvider Services { get; set; }
public async Task RunBotAsync()
{
Client = new DiscordSocketClient();
Commands = new CommandService();
Services = new ServiceCollection().AddSingleton(Client).AddSingleton(Commands).BuildServiceProvider();
string token = "ODQ1MzE1OTY2OTcxODA1NzI3.YKfL1w.SPXi_0xXbbrMziZ9JWiqHFX4dto";
Client.Log += ClientLog;
await RegisterCommandsAsync();
await Client.LoginAsync(TokenType.Bot, token);
await Client.StartAsync();
await Task.Delay(-1);
}
private Task ClientLog(LogMessage arg)
{
Console.WriteLine(arg);
return Task.CompletedTask;
}
public async Task RegisterCommandsAsync()
{
Client.MessageReceived += HandleCommandAsync;
await Commands.AddModulesAsync(Assembly.GetEntryAssembly(), Services);
}
I've tried using an async Main as suggested but it does not work
If you can't use async Main, then block in the Main method:
public static void Main(string[] args) => new Program().RunBotAsync().GetAwaiter().GetResult();
I saw the implementation of Stephen Cleary's Disposables NuGet package and it seems like it's perfect in my case, even tho, I couldn't find examples on how to inherit from it.
My idea is to make UnsubscribeAsync().GetAwaiter().GetResult(); to await UnsubscribeAsync();, which means it should be wrapped into an IAsyncDisposable. How could I achieve that with a sealed class?
public sealed class LiveTradeManager : ITradeManager, IDisposable
{
private bool _disposed;
private readonly ILogger<LiveTradeManager> _logger;
private readonly TradeOptions _tradeOptions;
private readonly IBotClient _client;
private string _listenKey;
private UpdateSubscription _candleSubscription, _accountUpdateSubscription;
private IDictionary<string, Channel<IBinanceStreamKlineData>> _channels;
public LiveTradeManager(ILogger<LiveTradeManager> logger, IOptions<TradeOptions> tradeOptions, IOptions<ExchangeOptions> exchangeOptions, IBotClientFactory clientFactory)
{
_logger = logger;
_tradeOptions = tradeOptions.Value;
_client = clientFactory.GetBotClient(exchangeOptions.Value.BotClientType);
}
public bool IsPaused { get; set; }
public async Task RunAsync(CancellationToken cancellationToken)
{
try
{
await SubscribeAsync(cancellationToken).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
}
catch (Exception ex) when (Handle(() => _logger.LogError(ex, "Unexpected error.")))
{
}
}
private async Task SubscribeAsync(CancellationToken cancellationToken)
{
// Subscribe to account updates
_listenKey = await _client.GetListenKeyAsync(cancellationToken).ConfigureAwait(false);
void OnOrderUpdate(BinanceStreamOrderUpdate order)
{
// order update logic
}
_accountUpdateSubscription = await _client.SubscribeToUserDataUpdatesAsync(_listenKey, OnOrderUpdate).ConfigureAwait(false);
_ = Task.Run(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
await _client.KeepAliveListenKeyAsync(_listenKey, cancellationToken).ConfigureAwait(false);
await Task.Delay(TimeSpan.FromMinutes(30), cancellationToken).ConfigureAwait(false);
}
}, cancellationToken);
// Subscribe to candle updates
var symbols = _tradeOptions.Symbols.Select(x => x.ToString()).ToList();
_channels = symbols.ToDictionary(x => x, _ =>
Channel.CreateBounded<IBinanceStreamKlineData>(new BoundedChannelOptions(1)
{FullMode = BoundedChannelFullMode.DropOldest}));
async void OnCandleReceived(IBinanceStreamKlineData data)
{
if (IsPaused) return;
try
{
var ohlcv = data.Data.ToCandle();
if (data.Data.Final)
{
_logger.LogInformation(
$"[{data.Symbol}] Finalized candle | Open time: {ohlcv.Timestamp.ToDateTimeFormat()} | Price: {ohlcv.Close}");
_ = Task.Run(async () =>
{
await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
}, cancellationToken);
}
else
{
_logger.LogInformation(
$"[{data.Symbol}] Candle update | Open time: {ohlcv.Timestamp.ToDateTimeFormat()} | Price: {ohlcv.Close}");
await _channels[data.Symbol].Writer.WriteAsync(data, cancellationToken).ConfigureAwait(false);
}
}
catch (TaskCanceledException)
{
}
}
_candleSubscription = await _client
.SubscribeToCandleUpdatesAsync(symbols, KlineInterval.OneMinute, OnCandleReceived)
.ConfigureAwait(false);
var tasks = _channels.Values.Select(async channel =>
{
await foreach (var data in channel.Reader.ReadAllAsync(cancellationToken))
{
// long-running logic...
await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
}
});
// NOTE: this would block further logic
await Task.WhenAll(tasks).ConfigureAwait(false);
}
private async Task UnsubscribeAsync()
{
// Unsubscribe account updates
if (!string.IsNullOrEmpty(_listenKey))
{
await _client.StopListenKeyAsync(_listenKey).ConfigureAwait(false);
}
if (_accountUpdateSubscription != null)
{
await _client.UnsubscribeAsync(_accountUpdateSubscription).ConfigureAwait(false);
}
// Unsubscribe candle updates
if (_candleSubscription != null)
{
await _client.UnsubscribeAsync(_candleSubscription).ConfigureAwait(false);
}
// Channels
if (_channels != null)
{
foreach (var channel in _channels.Values)
{
channel.Writer.Complete();
}
_channels.Clear();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
UnsubscribeAsync().GetAwaiter().GetResult();
}
_disposed = true;
}
}
public class BotManagerService : BackgroundService
{
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IDiscordClient _discordClient;
private readonly ITradeManager _tradeManager;
public BotManagerService(
IHostApplicationLifetime hostApplicationLifetime,
IOptions<ExchangeOptions> options,
IDiscordClient discordClient,
ITradeManagerFactory tradeManagerFactory)
{
_hostApplicationLifetime = hostApplicationLifetime;
_discordClient = discordClient;
_tradeManager = tradeManagerFactory.GetTradeManager(options.Value.TradeManagerType);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
var task1 = _tradeManager.RunAsync(stoppingToken);
var task2 = _discordClient.StartAsync();
await Task.WhenAll(task1, task2).ConfigureAwait(false);
}
finally
{
_hostApplicationLifetime.StopApplication();
}
}
}
I couldn't find examples on how to inherit from it.
Nito.Disposables, like the vast majority of the code I write, is written for composition rather than inheritance.
So, if you have a type that needs to implement IAsyncDisposable, it should contain an IAsyncDisposable implementation and forward its interface methods to that contained object:
public sealed class LiveTradeManager : ITradeManager, IAsyncDisposable
{
private readonly AsyncDisposable _disposable;
...
public LiveTradeManager(...)
{
...
_disposable = new(async () => await UnsubscribeAsync());
}
public ValueTask DisposeAsync() => _disposable.DisposeAsync();
}
If I call Stop(), OperationCanceledException is happened and _writer.TryComplete(exp) is true. But _reader.Completion Task is still not completed.
Is it desired behavior of Channels? If yes can someone tell me how to stop a Channel without waiting till it's empty and have its Completion Task in Completed state?
public interface IItem
{
Uri SourceUri { get; }
string TargetPath { get; }
}
public class Item : IItem
{
public Item(Uri sourceUri, string targetPath)
{
SourceUri = sourceUri;
TargetPath = targetPath;
}
public Uri SourceUri { get; }
public string TargetPath { get; }
}
public class TestService
{
private readonly ChannelWriter<IItem> _writer;
private readonly ChannelReader<IItem> _reader;
private readonly CancellationTokenSource _cts;
public TestService()
{
_cts = new CancellationTokenSource();
Channel<IItem> channel = Channel.CreateUnbounded<IItem>();
_reader = channel.Reader;
_writer = channel.Writer;
}
public async Task QueueDownload(IItem information)
{
await _writer.WriteAsync(information);
}
public void StartDownload()
{
Task.Factory.StartNew(async () =>
{
await ProcessDownloadAsync();
}, TaskCreationOptions.LongRunning);
}
public void Stop()
{
_cts.Cancel();
//_writer.Complete();
//_writer = null;
Console.WriteLine("Stop");
}
public async Task Wait()
{
await _reader.Completion;
}
private async Task ProcessDownloadAsync()
{
try
{
while (await _reader.WaitToReadAsync(_cts.Token))
{
IItem information = await _reader.ReadAsync(_cts.Token);
using (WebClient webClient = new WebClient())
{
Console.WriteLine(information.TargetPath);
await webClient.DownloadFileTaskAsync(information.SourceUri,
information.TargetPath);
}
}
}
catch (OperationCanceledException exp)
{
bool res = _writer.TryComplete(exp);
}
}
}
static class Program
{
static async Task Main(string[] args)
{
TestService tSvc = new TestService();
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
tSvc.StartDownload();
Task t = tSvc.Wait();
tSvc.Stop();
await t;
Console.WriteLine("Finished");
}
}
The ChannelWriter.Complete method behaves a bit differently than one would expect. It is not invalidating instantly the contents of the channel. Instead, it just prevents adding more items in the channel. The existing items are still valid for consumption, and the ChannelReader.Completion property will not complete before all stored items are consumed.
The example below demonstrates this behavior:
var channel = Channel.CreateUnbounded<int>();
channel.Writer.TryWrite(1);
channel.Writer.Complete(new FileNotFoundException());
//channel.Reader.TryRead(out var data);
var completed = channel.Reader.Completion.Wait(500);
Console.WriteLine($"Completion: {(completed ? "OK" : "Timed-out")}");
Output:
Completion: Timed-out
You can uncomment the channel.Reader.TryRead line, to see the FileNotFoundException to emerge.
Is it possible to await on a callback from a Event Aggregator such as prism?
MessageManager.Subscribe("Reply", this.GetType().Name, Myfunc);
private void Myfunc(object obj)
{
}
public async void MyFunc2()
{
MessageManager.Publish("Request", xxx);
await [Message Reply]
}
There is the ugly method of Subscribe followed by a unsubscribe in a lambda but this is not very pretty.
Is there a better way to do that?
EDIT:
This is the solution I have so far:
private SemaphoreSlim dataReady = new SemaphoreSlim(0, 1);
MeasurementDataSet lastDataSet;
public Calibrator(IMessageManager msgMgr)
{
MessageManager = msgMgr;
MessageManager.Subscribe("NewMeasurementData", this.GetType().Name, OnMeasurementData);
}
private void OnMeasurementData(object obj)
{
lastDataSet = (MeasurementDataSet) obj;
dataReady.Release();
}
public async void Calibrate(IParameterCache prmCache, Action<string> updateText)
{
MessageManager.Publish("InitiateMeasurement", "CAL ");
await dataReady.WaitAsync();
// do stuff
MessageManager.Publish("InitiateMeasurement", "CAL ");
await dataReady.WaitAsync();
// do stuff
MessageManager.Publish("InitiateMeasurement", "CAL ");
await dataReady.WaitAsync();
// do stuff
}
You can use a TaskCompletionSource<T> to await an event
public partial class MainWindow : Window
{
IEventAggregator _eventAggregator;
public MainWindow()
{
InitializeComponent();
_eventAggregator = new EventAggregator();
_eventAggregator.GetEvent<PubSubEvent<Request>>().Subscribe( payload =>
{
Thread.Sleep( 1000 ); // only fake work
_eventAggregator.GetEvent<PubSubEvent<Response>>().Publish( new Response() );
}, ThreadOption.BackgroundThread );
}
private async void Button_Click( object sender, RoutedEventArgs e )
{
( (Button)sender ).IsEnabled = false;
try
{
var tcs = new TaskCompletionSource<Response>();
using ( var token = _eventAggregator.GetEvent<PubSubEvent<Response>>().Subscribe( payload =>
{
tcs.TrySetResult( payload );
}, ThreadOption.BackgroundThread ) )
{
_eventAggregator.GetEvent<PubSubEvent<Request>>().Publish( new Request() );
await tcs.Task;
}
}
finally
{
( (Button)sender ).IsEnabled = true;
}
}
}
public class Request
{
}
public class Response
{
}
I have these functions
public async Task<List<Machine>> GetMachines()
{
await Initialize();
await SyncMachines();
return await machineTable.ToListAsync();
}
public async Task InsertMachines(List<Machine> machines)
{
await Initialize();
await Task.WhenAll(machines.Select(m => machineTable.InsertAsync(m)));
await SyncMachines();
}
I am writing a parent class to put these functions in, such that the functions getMachines() and InsertMachines() become getObject and InsertObject where the List<Machine> can be a list of any objects, so they can return and accept any type of list
How do I declare functions like this?
According to your description, I created my azure sync table class as follows, you could refer to it:
public class AzureCloudSyncTable<TModel> where TModel : class
{
public static MobileServiceClient MobileService = new MobileServiceClient(
"https://{your-mobile-app-name}.azurewebsites.net"
);
private IMobileServiceSyncTable<TModel> syncTable = MobileService.GetSyncTable<TModel>();
#region Offline sync
private async Task InitLocalStoreAsync()
{
if (!MobileService.SyncContext.IsInitialized)
{
var store = new MobileServiceSQLiteStore("localstore.db");
store.DefineTable<TModel>();
await MobileService.SyncContext.InitializeAsync(store);
}
await SyncAsync();
}
private async Task SyncAsync()
{
await PushAsync();
await syncTable.PullAsync(typeof(TModel).Name, syncTable.CreateQuery());
}
private async Task PushAsync()
{
try
{
await MobileService.SyncContext.PushAsync();
}
catch (MobileServicePushFailedException ex)
{
if (ex.PushResult != null)
{
foreach (var error in ex.PushResult.Errors)
{
await ResolveConflictAsync(error);
}
}
}
}
private async Task ResolveConflictAsync(MobileServiceTableOperationError error)
{
//var serverItem = error.Result.ToObject<TModel>();
//var localItem = error.Item.ToObject<TModel>();
//// Note that you need to implement the public override Equals(TModel item)
//// method in the Model for this to work
//if (serverItem.Equals(localItem))
//{
// // Items are the same, so ignore the conflict
// await error.CancelAndDiscardItemAsync();
// return;
//}
//// Client Always Wins
//localItem.Version = serverItem.Version;
//await error.UpdateOperationAsync(JObject.FromObject(localItem));
// Server Always Wins
//await error.CancelAndDiscardItemAsync();
}
#endregion
#region public methods
public async Task<List<TModel>> GetAll()
{
await InitLocalStoreAsync();
await SyncAsync();
return await syncTable.ToListAsync();
}
public async Task Insert(List<TModel> items)
{
await InitLocalStoreAsync();
await Task.WhenAll(items.Select(item => syncTable.InsertAsync(item)));
await PushAsync();
}
#endregion
}
Additionally, for more details about Handling Conflict Resolution, you could refer to adrian hall's book here.