I was wondering how to implement this Android AsyncTask using .NET's async libraries:
public class LoadRecordTask : AsyncTask
{
private Activity1 _context;
private int _recordId;
public LoadRecordTask( Activity1 outerActivity, int recordId )
{
_context = outerActivity;
_recordId = recordId;
}
protected override void OnPreExecute()
{
base.OnPreExecute();
_progressDialog = Android.App.ProgressDialog.Show( _context , "", "Loading record {0}", _recordId );
}
protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] #params)
{
_bSuccess = LoadRecord( _recordId );
if( !_bSuccess )
{
return false;
}
return true;
}
protected override void OnPostExecute(Java.Lang.Object result)
{
base.OnPostExecute(result);
if( !_bSuccess )
{
_progressDialog.SetMessage( "Error loading recording." );
}
_progressDialog.Dismiss();
}
}
The AsyncTask you are using could be translated to something like:
_progressDialog = ProgressDialog.Show( _context , "", "Loading record {0}", _recordId );
if (await LoadRecord(_recordId))
_progressDialog.Dismiss();
else
_progressDialog.SetMessage( "Error loading recording." );
Where LoadRecord could be returning Task<bool> and the internals running inside of Task. Otherwise you can just wrap the LoadRecord method you are currently using in a Task to make it run async.
private Task<bool> LoadRecord(int recordId)
{
return Task<bool>.Run(() =>
{
//Do stuff here to fetch records
return true;
});
}
The method you are calling await from needs to be marked as async. I.e.:
private async void MyAwesomeAsyncMethod() {}
Related
Error text: InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
Code:
DI
builder.Services.AddDbContext<MedicardDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
builder.Services.AddTransient<IUnitOfWork, UnitOfWork>();
Controller:
public async Task<IActionResult> AllChats()
{
var currentUser = await _userManager.GetUserAsync(User);
var allUsers = await _chatService.GetAllTargetUsersBasedOnRole(currentUser);
var chatsViewModel = await _chatService.GetListChats(allUsers, currentUser);
return View(chatsViewModel);
}
ChatService:
public async Task<List<ChatViewModel>> GetListChats(List<User> allUsers, User currentUser)
{
var chatsViewModel = new List<ChatViewModel>();
foreach (var targetUser in allUsers)
{
var chat = new ChatViewModel
{
UserName = targetUser.UserName,
FullName = targetUser.ToString(),
};
var picture = await GetPictureForChat(targetUser);
if (picture == null)
{
var doctor = _unitOfWork.GenericRepository<Doctor>().GetAll(doctor => doctor.UserId == targetUser.Id).FirstOrDefault();
chat.Picture = doctor.DoctorPicture;
}
else
{
chat.Picture = picture;
}
chatsViewModel.Add(chat);
}
return chatsViewModel;
}
Method GetPictureForChat:
private async Task<string> GetPictureForChat(User user)
{
return await _userManager.IsInRoleAsync(user, "Admin") ? "admin.png" :
await _userManager.IsInRoleAsync(user, "Doctor") ? null : "user.png";
}
UnitOfWork:
public class UnitOfWork : IUnitOfWork, IDisposable
{
private readonly MedicardDbContext _context;
public UnitOfWork(MedicardDbContext context)
{
_context = context;
}
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_context.Dispose();
}
}
this.disposed = true;
}
public IGenericRepository<T> GenericRepository<T>() where T : class
{
IGenericRepository<T> repository = new GenericRepository<T>(_context);
return repository;
}
public void Save()
{
_context.SaveChanges();
}
public async Task SaveAsync()
{
await _context.SaveChangesAsync();
}
}
The error is written somewhere along these lines:
return await _userManager.IsInRoleAsync(user, "Admin") ? "admin.png" :
await _userManager.IsInRoleAsync(user, "Doctor") ? null : "user.png";
and
var picture = await GetPictureForChat(targetUser);
and
var chatsViewModel = await _chatService.GetListChats(allUsers, currentUser);
But I don't know how to solve it.
I have a WebAPI server with integrated SignalR Hubs.
The problem it is in the integration between both components and efficiently call the clients interested on a given item that was updated through REST with the least overhead possible on the Controller side.
I have read about Background tasks with hosted services in ASP.NET Core, or Publish Subscriber Patterns but they don't seem the right fit for this problem.
From the documentation examples, the background tasks seem to atempt to preserve order which is not required, in fact, it is desired to allow multiple requests to be handled concurrently, as efficiently as possible.
With this in mind, I created this third component called MappingComponent that is being called through a new Task.
It is important to design the Controller in a way that he spends the least amount of work "raising the events" possible. Exceptions should be (i believe) handled within the MappingComponent.
What would be a better approach/design pattern that the following implementation, to avoid using Task.Run?
ApiController
[Route("api/[controller]")]
[ApiController]
public class ItemController : ControllerBase
{
private readonly MappingComponent mappingComponent;
private readonly IDataContext dataContext;
[HttpPost]
public async Task<ActionResult<Item>> PostItem(ItemDTO itemDTO)
{
await dataContext.Items.AddAsync(item);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemAdd(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return CreatedAtAction("Get", new { id = item.Id }, item);
}
[HttpDelete("{id}", Name = "Delete")]
public async Task<IActionResult> Delete(int id)
{
var item = await dataContext.Items.FindAsync(id);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemDelete(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return NoContent();
}
[HttpPatch("{id}", Name = "Patch")]
public async Task<IActionResult> Patch(int id,
[FromBody] JsonPatchDocument<Item> itemToPatch)
{
var item = await dataContext.Items.FindAsync(id);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemEdit(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return StatusCode(StatusCodes.Status202Accepted);
}
}
SignalR Hub
public class BroadcastHub : Hub<IHubClient>
{
private readonly MappingComponent mappingComponent;
public BroadcastHub(MappingComponent mappingComponent)
{
this.mappingComponent = mappingComponent;
}
public override Task OnConnectedAsync()
{
mappingComponent.OnConnected(Context.User.Identity.Name, Context.ConnectionId));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync()
{
mappingComponent.OnDisconnected(Context.User.Identity.Name, Context.ConnectionId));
return base.OnDisconnectedAsync();
}
public void Subscribe(string itemQuery)
{
mappingComponent.SubscribeConnection(Context.User.Identity.Name, Context.ConnectionId, itemQuery));
}
public void Unsubscribe()
{
mappingComponent.UnsubscribeConnection(Context.ConnectionId));
}
}
"MappingComponent" being registered as singleton on startup
public class MappingComponent
{
private readonly IServiceScopeFactory scopeFactory;
private readonly IHubContext<BroadcastHub, IHubClient> _hubContext;
private static readonly ConcurrentDictionary<string, User> Users = new(StringComparer.InvariantCultureIgnoreCase);
private static readonly ConcurrentDictionary<int, List<string>> ItemConnection = new();
private static readonly ConcurrentDictionary<string, List<int>> ConnectionItem = new();
public MappingComponent(IServiceScopeFactory scopeFactory, IHubContext<BroadcastHub, IHubClient> hubContext)
{
//this.dataContext = dataContext;
this._hubContext = hubContext;
this.scopeFactory = scopeFactory;
}
internal void OnConnected(string userName, string connectionId){(...)}
internal void OnDisconnected(string userName, string connectionId){(...)}
internal void SubscribeConnection(string userName, string connectionId, string query){(...)}
internal void UnsubscribeConnection(string connectionId){(...)}
internal async Task NotifyOnItemAdd(Item item)
{
List<string> interestedConnections = new();
(...)
//Example containing locks
lock()
{
//There is a need to acess EF database
using (var scope = scopeFactory.CreateScope())
{
var dataContext = scope.ServiceProvider.GetRequiredService<IDataContext>()
await dataContext.Items.(...)
interestedConnections = ...
}
}
await _hubContext.Clients.Clients(interestedConnections).BroadcastItem(item);
}
internal async Task NotifyOnItemEdit(Item item)
{
List<string> interestedConnections = new();
(...)
await _hubContext.Clients.Clients(interestedConnections).BroadcastItem(item);
}
internal async Task NotifyOnItemDelete(Item item)
{
List<string> interestedConnections = new();
(...)
await _hubContext.Clients.Clients(interestedConnections).BroadcastAllItems();
}
}
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();
}
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
{
}
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?