Async method that calls in ViewModel causes deadlock - c#

I do requests to Github Api so I have async methods, these do this job. Before it, I always called they in method, that calls from command(actually DelegateCommand). But now I wanna do request in ViewModel because I need to display list on page. I am using Prism to wire view and viewmodel.
Because I can't make viewmodel async, I can't use await word, so I tried to do something like gets result from task, or task.wait. But with this I have the same result. My app stop works with white display when it did request. I read some info about that and I understood that call async method in not async method is bad, and it causes deadlock, but I don't know what to do with this. And I think deadlock causes that app stop works.
Here is method where app die:
public async Task<IEnumerable<RepositoryModel>> GetRepositoriesAsync()
{
try
{
var reposRequest = new RepositoryRequest { Sort = RepositorySort.FullName };
var gitHubRepos = await _gitHubClient.Repository.GetAllForCurrent(reposRequest); //async request, don't say about name convention, it is not my method.
var gitRemoteRepos = new List<RepositoryModel>();
foreach ( var repository in gitHubRepos )
{
var repos = new RepositoryModel();
repos.RepositoryTypeIcon = GetRepositoryTypeIcon(repository);
gitRemoteRepos.Add(repos);
}
return gitRemoteRepos;
}
catch ( WebException ex )
{
throw new Exception("Something wrong with internet connection, try to On Internet " + ex.Message);
}
catch ( Exception ex )
{
throw new Exception("Getting repos from github failed! " + ex.Message);
}
}
And here is viewmodel:
public class RepositoriesPageViewModel : BindableBase
{
private INavigationService _navigationService;
private readonly Session _session;
public ObservableCollection<RepositoryModel> Repositories { get; }
private readonly RepositoriesManager _repositoriesManager;
public RepositoriesPageViewModel(INavigationService navigationService, ISecuredDataProvider securedDataProvider)
{
_navigationService = navigationService;
var token = securedDataProvider.Retreive(ConstantsService.ProviderName, UserManager.GetLastUser());
_session = new Session(UserManager.GetLastUser(), token.Properties.First().Value);
var navigationParameters = new NavigationParameters { { "Session", _session } };
_repositoriesManager = new RepositoriesManager(_session);
var task = _repositoriesManager.GetRepositoriesAsync();
//task.Wait();
Repositories = task.Result as ObservableCollection<RepositoryModel>;
}
}

I recommend using my NotifyTask<T> type, which provides a data-bindable wrapper around Task<T>. I explain this pattern more completely in my article on async MVVM data binding.
public class RepositoriesPageViewModel : BindableBase
{
private INavigationService _navigationService;
private readonly Session _session;
public NotifyTask<ObservableCollection<RepositoryModel>> Repositories { get; }
private readonly RepositoriesManager _repositoriesManager;
public RepositoriesPageViewModel(INavigationService navigationService, ISecuredDataProvider securedDataProvider)
{
_navigationService = navigationService;
var token = securedDataProvider.Retreive(ConstantsService.ProviderName, UserManager.GetLastUser());
_session = new Session(UserManager.GetLastUser(), token.Properties.First().Value);
var navigationParameters = new NavigationParameters { { "Session", _session } };
_repositoriesManager = new RepositoriesManager(_session);
Repositories = NotifyTask.Create(GetRepositoriesAsync());
}
}
private async Task<ObservableCollection<RepositoryModel>> GetRepositoriesAsync()
{
return new ObservableCollection<RepositoryModel>(await _repositoriesManager.GetRepositoriesAsync());
}
Note that with this approach, your data binding would use Repositories.Result to access the actual collection. Other properties are also available, most notably Repositories.IsCompleted and Respositories.IsNotCompleted for showing/hiding busy spinners.

Related

WPF SimpleInjector call to client.GetAsync hanging

I am trying to use SimpleInjector in a WPF Application (.NET Framework). We use it in exactly the same way in many of our Services but for some reason when I am attempting to implement the same logic in this WPF Application, the call to the HttpClient().GetAsync is hanging. We think it is because for some reason the Task is not executing.
I am registering the objects from the OnStartUp element of App.xaml.cs as below. Inside the SetupService Constructor we call a SetupService URL (set in the SetupConfiguration Section of the App.Config) to get the SetupResponse to use in the app.
It is ultimately hanging in the ServiceClient.GetAsync method, I have tried to show the flow below:
All classes appear to have been injected correctly, and the ServiceClient is populated in exactly the same way as the same point in one of our working services. We're at a loss as to what is happening, and how to fix this.
Finally, SetupService is being injected in other Classes - so I would rather get it working like this, rather than remove the call from the SimpleInjector mechanism.
Any help is very much appreciated.
public partial class App : Application
{
private static readonly Container _container = new Container();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
RegisterDependencies();
_container.Verify();
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
public class SetupService: ISetupService
{
private static readonly Dictionary<string, string> AcceptType = new Dictionary<string, string>
{
{"Accept", "application/xml"}
};
private const string AuthenticationType = "Basic";
private readonly IServiceClient _serviceClient;
private readonly ILoggingProvider _logger;
private readonly IConfigurationSection _configuration;
public SetupService(IConfigurationSection configuration, IServiceClient serviceClient, ILoggingProvider logger)
{
_serviceClient = serviceClient;
_logger = logger;
_configuration = kmsConfiguration;
RefreshSetup();
}
public void RefreshSetup()
{
try
{
var token = BuildIdentityToken();
var authHeaderClear = string.Format("IDENTITY_TOKEN:{0}", token);
var authenticationHeaderValue =
new AuthenticationHeaderValue(AuthenticationType, Convert.ToBase64String(Encoding.ASCII.GetBytes(authHeaderClear)));
_serviceClient.Url = _configuration.Url;
var httpResponse = _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType).Result;
var responseString = httpResponse.Content.ReadAsStringAsync().Result;
_response = responseString.FromXML<SetupResponse>();
}
catch (Exception e)
{
throw
}
}
public class ServiceClient : IServiceClient
{
private const string ContentType = "application/json";
private string _userAgent;
private ILoggingProvider _logger;
public string Url { get; set; }
public string ProxyAddress { get; set; }
public int TimeoutForRequestAndResponseMs { get; set; }
public int HttpCode { get; private set; }
public ServiceClient(ILoggingProvider logger = null)
{
_logger = logger;
}
public async Task<HttpResponseMessage> GetAsync(string endpoint, AuthenticationHeaderValue authenticationHeaderValue = null, IDictionary<string, string> additionalData = null, IDictionary<string, string> additionalParams = null)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Url);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType));
if (authenticationHeaderValue != null)
client.DefaultRequestHeaders.Authorization = authenticationHeaderValue;
ProcessHeader(client.DefaultRequestHeaders, additionalData);
var paramsQueryString = ProcessParams(additionalParams);
if (!string.IsNullOrEmpty(paramsQueryString))
endpoint = $"{endpoint}?{paramsQueryString}";
return await client.GetAsync(endpoint); **// HANGS ON THIS LINE!**
}
}
}
}
If you block on asynchronous code from a UI thread, then you can expect deadlocks. I explain this fully on my blog. In this case, the cause of the deadlock is Result. There's a couple of solutions.
The one I recommend is to rethink your user experience. Your UI shouldn't be blocking on an HTTP call to complete before it shows anything; instead, immediately (and synchronously) display a UI (i.e., some "loading..." screen), and then update that UI when the HTTP call completes.
The other is to block during startup. There's a few patterns for this. None of them work in all situations, but one that usually works is to wrap the asynchronous work in Task.Run and then block on that, e.g., var httpResponse = Task.Run(() => _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType)).GetAwaiter().GetResult(); and similar for other blocking calls.
Blocking before showing a UI is generally considered a bad UX. App stores generally disallow it. So that's why I recommend changing the UX. You may find an approach like this helpful.
Thanks for your Responses, I just wanted to sync the solution I've gone for.
It was risky for me to change the code in SetupService to remove the .Result, even though this was probably the correct solution, as I did not want to affect the other working Services using the SetupService library already there.
I ended up moving the regsitrations off the UI Thread by embedding the SimpleInjector code in a Code library, Creating a Program.cs and Main() and setting that as my Entry point.
static class Program
{
public static readonly Container _container = new Container();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(){
var app = new MyApp.App();
Register();
app.Run(_container.GetInstance<MainWindow>());
}
static void Register()
{
_container.Register<MainWindow>();
MySimpleInjector.Register(_container);
_container.Verify();
}
}
and then, in a Separate .dll project, MyApp.Common
public class MySimpleInjector
{
private readonly Container _container;
public static void Register(Container container)
{
var injector = new MySimpleInjector(container);
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
I appreciate that this may not be the ideal solution - but it suits my purposes.
Again, thanks for your help and comments!
Andrew.

C# - How to assign the result of GetFromJsonAsync to a variable, and use it without it repeatedly calling the GET request?

I am trying to assign a class property with the result of a GET request, such that all other methods in the class can use the value of that property without having to call the GET request more than once. Bottom line is I want to call the GET Request during class instantiaton, assign it to a variable, and then never call it again during the objects lifetime.
public class Example {
private readonly HttpClient _http;
private readonly List<Thing> _things;
public Example(HttpClient http)
{
_http = http;
_things = _http.GetFromJsonAsync<List<Thing>>("https://api-to-call/endpoint").Result;
}
public void UseThings()
{
// Do something with _things;
}
}
However, when the method is called, it is sending a new GET request to retrieve an updated value of the property I assigned in the constructor. How can I code this so it only calls the GET request once during object instantiation?
You should not block in the constructor and .Result can have nasty side-effects. While there have been discussions of a language feature to support this, until then, you should move this out to a cached operation. You can ensure that the operation only runs once by wrapping the http call with a SemaphoreSlim.
public class Example
{
private readonly HttpClient _http;
private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
private List<Thing> _things;
public Example(HttpClient http)
{
_http = http;
}
public async Task UseThings()
{
// Do something with _things;
var localThings = _things ?? await GetThingsAsync();
}
private async Task<List<Thing>> GetThingsAsync()
{
if (_things != null)
{
return await Task.FromResult(_things);
}
await _lock.WaitAsync();
try
{
// double check in case another thread has completed
if (_things != null)
{
return _things;
}
_things = await _http.GetFromJsonAsync<List<Thing>>("https://api-to-call/endpoint");
return _things;
}
finally
{
_lock.Release();
}
}
}

When do Injection dependency Integration Event, controller not called c#

I am trying to make communication between microservices using eventbus, when I use dependency injection my controller can no longer be called.
I have my Controller
public class CustomersController : ControllerBase
{
private readonly ICustomerRepository _customerRepository;
private readonly IIdentityService _identityService;
private readonly ICustomerIntegrationEventService _customerIntegrationEventService;
public CustomersController(ICustomerRepository customerRepository, IIdentityService identityService, ICustomerIntegrationEventService customerIntegrationEventService)
{
_customerRepository = customerRepository ?? throw new ArgumentNullException(nameof(customerRepository));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_customerIntegrationEventService = customerIntegrationEventService ?? throw new ArgumentNullException(nameof(customerIntegrationEventService));
}
}
In this Controller, I have a method named Add. It basically adds a client. When client is added, I would like to notify other microservice and send the data to a service bus. So far I'm using Integration Event. But in the moment that the dependency injection is done in the controller. the front can no longer hit the controller, returning an error 500.
public async Task<IActionResult> Add(Customer value)
{
var idAdded = await _customerRepository.Add(value).ConfigureAwait(false);
if (!idAdded.HasValue)
return BadRequest();
var integrationEvent = new CustomerIntegrationEvent(idAdded.Value, value);
await _customerIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
return CreatedAtAction(nameof(Get), new { id = idAdded.Value }, null);
}
Soon below is how this class building _customerIntegrationEventService
CustomerIntegrationEventService
public class CustomerIntegrationEventService : ICustomerIntegrationEventService
{
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
private readonly IEventBus _eventBus;
private readonly ApplicationDataContext _osDataContext;
private readonly IntegrationEventLogContext _eventLogContext;
private readonly IIntegrationEventLogService _eventLogService;
public CustomerIntegrationEventService(
IEventBus eventBus,
ApplicationDataContext hrDataContext,
IntegrationEventLogContext eventLogContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
{
_osDataContext = hrDataContext ?? throw new ArgumentNullException(nameof(hrDataContext));
_eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext));
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_eventLogService = _integrationEventLogServiceFactory(hrDataContext.Database.GetDbConnection());
}
public async Task PublishEventsThroughEventBusAsync()
{
var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync();
foreach (var logEvt in pendindLogEvents)
{
try
{
await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId);
_eventBus.Publish(logEvt.IntegrationEvent);
await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId);
}
catch (Exception)
{
await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId);
}
}
}
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
{
await _eventLogService.SaveEventAsync(evt, _osDataContext.Database.CurrentTransaction.GetDbTransaction());
}
}
all of these codes were taken from the example https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/subscribe-events
I made the dependency injection in the startup, but anyway the error persists
public void AddIntegrationServices(IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
services.AddTransient<ICustomerIntegrationEventService, CustomerIntegrationEventService>();
}
How could I at least see the error behind things, or how I come up with that solution. This code is based on microsoft eShopOnContainers

A second operation started on this context before a previous asynchronous operation completed with UnitofWork and async

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
My unitofwork code
public class UnitOfWork : IUnitOfWork
{
private readonly CAMSDbEntities _context;
private bool _disposed;
public Dictionary<Type, object> repositories = new Dictionary<Type, object>();
private Guid _objectId;
public UnitOfWork(IContextFactory contextFactory)
{
_context = contextFactory.DbContext as CAMSDbEntities;
_objectId = Guid.NewGuid();
}
public IGenericRepository<T> Repository<T>() where T : class
{
if (repositories.Keys.Contains(typeof(T)) == true)
{
return repositories[typeof(T)] as GenericRepository<T>;
}
GenericRepository<T> repo = new GenericRepository<T>(_context);
repositories.Add(typeof(T), repo);
return repo;
}
My unity config
container.RegisterType<IHttpContext, HttpContextObject>();
container.RegisterType<IDataBaseManager, DataBaseManager>();
container.RegisterType<IContextFactory, ContextFactory>();
container.RegisterType(typeof(IGenericRepository<>), typeof(GenericRepository<>));
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<IAnalytics, DashbordService>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
webApi Controller
public class DashbordController : ApiController
{
private static IAnalytics _analytics;
public DashbordController(IAnalytics dashbordService)
{
_analytics = dashbordService;
}
[HttpGet]
[Route("GetStudentAssessmentHistory")]
public IHttpActionResult GetStudentAssessmentHistory(int studentID)
{
var result = _analytics.GetStudentAssessmentHistoryGraphData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetStudentFeePaymentHistory")]
public async Task<IHttpActionResult> GetStudentFeePaymentData(int studentID)
{
var result = await _analytics.GetStudentFeePaymentData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerHitoryByDepartment")]
public async Task<IHttpActionResult> GetLedgerHitoryByDepartment(int schoolID, int departmentId)
{
var result = await _analytics.GetLedgerHitory(schoolID, departmentId);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerExpenseTrendByDepartment")]
public async Task<IHttpActionResult> GetLedgerExpenseTrendByDepartment(int schoolID)
{
var result = await _analytics.GetLedgerExpenseTrend(schoolID);
return Ok(result);
}
dashboardservice Code
public async Task<List<LedgerExpense>> GetLedgerExpenseTrend(int schoolId)
{
try
{
var ledgerExpenses = new List<LedgerExpense>();
var currentDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, INDIAN_ZONE);
DateTime previoYearDate = currentDate.AddYears(-1);
var ledgerPayments = await _unitOfWork.Repository<LedgerDetail>().GetManyAsync(x => x.SchoolID == schoolId && x.PaymentDate <= currentDate
&& x.PaymentDate >= previoYearDate);
foreach (var ledgerPayment in ledgerPayments.OrderBy(x => x.PaymentDate).GroupBy(y => y.DepartmentID))
{
var department = await _unitOfWork.Repository<DeptartmentType>().GetAsync(x => x.ID == ledgerPayment.Key);
var ledgerData = new LedgerExpense
{
Department = department.DepartmentName,
TotalLedgerExpense = 0
};
foreach (var departmentPayment in ledgerPayment)
{
ledgerData.TotalLedgerExpense += departmentPayment.TotalPaidAmount;
}
ledgerExpenses.Add(ledgerData);
}
return ledgerExpenses;
}
catch (Exception ex)
{
logger.Log("An error occurred while fetching ledger expenses");
return null;
}
}
I have similar type of asynchronous metods implemented in my dashboardservice code. whenever I request a dashboard UI all request comes to the same controller at the same time and creates the new object for unitofwork and dbcontext for each request one by one. it works perfectly sometimes but Sometimes I think unitofwork and dbcontext object flows with the wrong thread and throws this error. I think somehow its picking wrong dbcontext which is already busy with someother api request from dashboard service.
Please remove the static keyword in your controller from this code:
private static IAnalytics _analytics;`
Once that has been created, it will never be created again unless the application pool is recycled (manual or IIS restart etc.) Since you are using the same instance for all requests, you are getting that error at random. If a request finishes before the next one arrives, it will NOT result in an error. Otherwise it will. Hence the reason for not always getting the error (as you mention in your question).
Please read about how static affects the design in a web scenario (or server).
Try and think of web requests as a single transaction, all classes are created for each request and then thrown away after the request has been served. That means if you have static or any other mechanism which is for sharing, it will be shared between requests.

Timing issue between async initialisation and results loading in application startup

Seeking some input on a behaviour I'm noticing in my code below. This is my first attempt at async/await using Xamarin Forms and I have perused hundreds of posts, blogs and articles on the subject including the writings from Stephen Cleary on async from constructors and best practices to avoid locking. Although I am using a MVVM framework I assume my issue is more generic than that so I'll ignore it for the moment here.
If I am still missing something or there are ways to improve what I'm trying to do ... happy to listen and learn.
At a high level the logic is as follows:
Application starts and initialises
During initialisation verify database exist and if not - create the SQLite DB. Currently I force this every time to simulate a new application and pre-populate it with some sample data for development purposes
After initialisation completed load results set and display
This works most of the time but I have noticed 2 infrequent occurrences due to the async handling of the database initialisation and pre-populating:
Occasionally not all sample records created are displayed once the app started up - I assume this is because the pre-population phase has not completed when the results are loaded
Occasionally I get an error that one of the tables have not been created - I assume this is because the database initialisation has not completed when the results are loaded
The code - simplified to show the flow during initialisation and startup:
----------- VIEW / PAGE MODEL ----------------
public class MyListItemsPageModel
{
private ObservableRangeCollection<MyListItem> _myListItems;
private Command loadItemsCommand;
public MyListItemsPageModel()
{
_myListItems = new ObservableRangeCollection<MyListItem>();
}
public override void Init(object initData)
{
if (LoadItemsCommand.CanExecute(null))
LoadItemsCommand.Execute(null);
}
public Command LoadItemsCommand
{
get
{
return loadItemsCommand ?? (loadItemsCommand = new Command(async () => await ExecuteLoadItemsAsyncCommand(), () => { return !IsBusy; }));
}
}
public ObservableRangeCollection<MyListItem> MyListItems {
get { return _myListItems ?? (_myListItems = new ObservableRangeCollection<MyListItem>()); }
private set {
_myListItems = value;
}
}
private async Task ExecuteLoadItemsAsyncCommand() {
if (IsBusy)
return;
IsBusy = true;
loadItemsCommand.ChangeCanExecute();
var _results = await MySpecificDBServiceClass.LoadAllItemsAsync;
MyListItems = new ObservableRangeCollection<MyListItem>(_results.OrderBy(x => x.ItemName).ToList());
IsBusy = false;
loadItemsCommand.ChangeCanExecute();
}
}
----------- DB Service Class ----------------
// THERE IS A SPECIFIC SERVICE LAYER BETWEEN THIS CLASS AND THE PAGE VIEW MODEL HANDLING THE CASTING OF TO THE SPECIFIC DATA TYPE
// public class MySpecificDBServiceClass : MyGenericDBServiceClass
public class MyGenericDBServiceClass<T>: IDataAccessService<T> where T : class, IDataModel, new()
{
public SQLiteAsyncConnection _connection = FreshIOC.Container.Resolve<ISQLiteFactory>().CreateConnection();
internal static readonly AsyncLock Mutex = new AsyncLock();
public DataServiceBase()
{
// removed this from the constructor
//if (_connection != null)
//{
// IsInitialized = DatabaseManager.CreateTableAsync(_connection);
//}
}
public Task<bool> IsInitialized { get; private set; }
public virtual async Task<List<T>> LoadAllItemsAsync()
{
// Temporary async/await initialisation code. This will be moved to the start up as per Stephen's suggestion
await DBInitialiser();
var itemList = new List<T>();
using (await Mutex.LockAsync().ConfigureAwait(false))
{
itemList = await _connection.Table<T>().ToListAsync().ConfigureAwait(false);
}
return itemList;
}
}
----------- DB Manager Class ----------------
public class DatabaseManager
{
static double CURRENT_DATABASE_VERSION = 0.0;
static readonly AsyncLock Mutex = new AsyncLock();
private static bool IsDBInitialised = false;
private DatabaseManager() { }
public static async Task<bool> CreateTableAsync(SQLiteAsyncConnection CurrentConnection)
{
if (CurrentConnection == null || IsDBInitialised)
return IsDBInitialised;
await ProcessDBScripts(CurrentConnection);
return IsDBInitialised;
}
private static async Task ProcessDBScripts(SQLiteAsyncConnection CurrentConnection)
{
using (await Mutex.LockAsync().ConfigureAwait(false))
{
var _tasks = new List<Task>();
if (CURRENT_DATABASE_VERSION <= 0.1) // Dev DB - recreate everytime
{
_tasks.Add(CurrentConnection.DropTableAsync<Table1>());
_tasks.Add(CurrentConnection.DropTableAsync<Table2>());
await Task.WhenAll(_tasks).ConfigureAwait(false);
}
_tasks.Clear();
_tasks.Add(CurrentConnection.CreateTableAsync<Table1>());
_tasks.Add(CurrentConnection.CreateTableAsync<Table2>());
await Task.WhenAll(_tasks).ConfigureAwait(false);
_tasks.Clear();
_tasks.Add(UpgradeDBIfRequired(CurrentConnection));
await Task.WhenAll(_tasks).ConfigureAwait(false);
}
IsDBInitialised = true;
}
private static async Task UpgradeDBIfRequired(SQLiteAsyncConnection _connection)
{
await CreateSampleData();
return;
// ... rest of code not relevant at the moment
}
private static async Task CreateSampleData()
{
IDataAccessService<MyListItem> _dataService = FreshIOC.Container.Resolve<IDataAccessService<MyListItem>>();
ObservableRangeCollection<MyListItem> _items = new ObservableRangeCollection<MyListItem>(); ;
_items.Add(new MyListItem() { ItemName = "Test 1", ItemCount = 14 });
_items.Add(new MyListItem() { ItemName = "Test 2", ItemCount = 9 });
_items.Add(new MyListItem() { ItemName = "Test 3", ItemCount = 5 });
await _dataService.SaveAllItemsAsync(_items).ConfigureAwait(false);
_items = null;
_dataService = null;
IDataAccessService<Sample> _dataService2 = FreshIOC.Container.Resolve<IDataAccessService<AnotherSampleTable>>();
ObservableRangeCollection<Sample> _sampleList = new ObservableRangeCollection<Sample>(); ;
_sampleList.Add(new GuestGroup() { SampleName = "ABC" });
_sampleList.Add(new GuestGroup() { SampleName = "DEF" });
await _dataService2.SaveAllItemsAsync(_sampleList).ConfigureAwait(false);
_sampleList = null;
_dataService2 = null;
}
}
In your DataServiceBase constructor, you're calling DatabaseManager.CreateTableAsync() but not awaiting it, so by the time your constructor exits, that method has not yet completed running, and given that it does very little before awaiting, it's probably barely started at that point. As you can't effectively use await in a constructor, you need to remodel things so you do that initialisation at some other point; e.g. perhaps lazily when needed.
Then you also want to not use .Result/.Wait() whenever possible, especially as you're in an async method anyway (e.g. ProcessDBScripts()), so instead of doing
var _test = CurrentConnection.DropTableAsync<MyListItem>().Result;
rather do
var _test = await CurrentConnection.DropTableAsync<MyListItem>();
You also don't need to use Task.Run() for methods that return Task types anyway. So instead of
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<MyListItem>().ConfigureAwait(false)));
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<AnotherSampleTable>().ConfigureAwait(false)));
just do
_tasks.Add(CurrentConnection.CreateTableAsync<MyListItem>()));
_tasks.Add(CurrentConnection.CreateTableAsync<AnotherSampleTable>()));
sellotape has correctly diagnosed the code problem: the constructor is starting an asynchronous method but nothing is (a)waiting for it to complete. A simple fix would be to add await IsInitialized; to the beginning of LoadAllItemsAsync.
However, there's also a design problem:
After initialisation completed load results set and display
That's not possible on Xamarin, or any other modern UI platform. You must load your UI immediately and synchronously. What you should do is immediately display a splash/loading page and start the asynchronous initialization work. Then, when the async init is completed, update your VM/UI with your "real" page. If you just have LoadAllItemsAsync await IsInitialized, then your app will sit there for some time showing the user zero data before it "fills in".
You may find my NotifyTask<T> type (available on NuGet) useful here if you want to show a splash/spinner instead of zero data.

Categories