在下面的代码中注意我正在注意的行为的一些输入。这是我第一次尝试使用Xamarin Forms进行异步/等待,我已经阅读了数百篇有关该主题的帖子,博客和文章,包括Stephen Cleary关于构造函数的异步和避免锁定的最佳实践的着作。虽然我使用的是MVVM框架,但我认为我的问题比这更通用,所以我暂时忽略它。
如果我仍然遗失某些东西,或者有办法改善我正在努力做的事情......乐于倾听和学习。
在高级别,逻辑如下:
这大部分时间都有用,但我注意到由于数据库初始化和预填充的异步处理导致出现了2次偶然事件:
代码 - 简化以显示初始化和启动期间的流程:
-----------查看/页面模型----------------
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();
}
}
-----------数据库服务类----------------
//此类和页面视图模型之间有特定的服务层处理特定数据类型的铸造
// 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;
}
}
答案 0 :(得分:0)
在你的DataServiceBase
构造函数中,你正在调用DatabaseManager.CreateTableAsync()
但是没有等待它,所以当你的构造函数退出时,该方法还没有完成运行,并且考虑到它之前很少等待,它可能在那时几乎没有开始。由于您无法在构造函数中有效地使用await,因此您需要对事物进行重新构建,以便在其他点进行初始化;例如或许在需要的时候可能很懒散。
然后你也希望不尽可能使用.Result
/ .Wait()
,尤其是当你处于异步方法时(例如ProcessDBScripts()
),所以不要做
var _test = CurrentConnection.DropTableAsync<MyListItem>().Result;
而不是
var _test = await CurrentConnection.DropTableAsync<MyListItem>();
对于返回Task.Run()
类型的方法,您也不需要使用Task
。而不是
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<MyListItem>().ConfigureAwait(false)));
_tasks.Add(Task.Run(() => CurrentConnection.CreateTableAsync<AnotherSampleTable>().ConfigureAwait(false)));
只是做
_tasks.Add(CurrentConnection.CreateTableAsync<MyListItem>()));
_tasks.Add(CurrentConnection.CreateTableAsync<AnotherSampleTable>()));
答案 1 :(得分:0)
sellotape已正确诊断出代码问题:构造函数正在启动异步方法,但没有(a)等待它完成。一个简单的解决方法是将await IsInitialized;
添加到LoadAllItemsAsync
的开头。
然而,还存在一个设计问题:
初始化完成后,加载结果设置并显示
在Xamarin或任何其他现代UI平台上,这是不可能的。您必须立即和同步加载您的UI。您应该做的是立即显示启动/加载页面并启动异步初始化工作。然后,当async init完成后,使用&#34; real&#34;更新您的VM / UI。页。如果您只有LoadAllItemsAsync
等待IsInitialized
,那么您的应用会在那里停留一段时间,以显示用户零数据,然后再填充&#34;填写&#34;。
如果你想显示一个启动/微调器而不是零数据,你可能会发现我的NotifyTask<T>
type(在NuGet上可用)很有用。