应用程序启动时异步初始化和结果加载之间的时间问题

时间:2017-01-05 03:24:07

标签: c# asynchronous mvvm xamarin async-await

在下面的代码中注意我正在注意的行为的一些输入。这是我第一次尝试使用Xamarin Forms进行异步/等待,我已经阅读了数百篇有关该主题的帖子,博客和文章,包括Stephen Cleary关于构造函数的异步和避免锁定的最佳实践的着作。虽然我使用的是MVVM框架,但我认为我的问题比这更通用,所以我暂时忽略它。

如果我仍然遗失某些东西,或者有办法改善我正在努力做的事情......乐于倾听和学习。

在高级别,逻辑如下:

  • 应用程序启动并初始化
  • 在初始化期间验证数据库是否存在,如果不存在 - 创建SQLite DB。 目前我每次强制执行此操作来模拟新应用程序并使用一些示例数据预先填充它以用于开发目的
  • 初始化完成后,加载结果设置并显示

这大部分时间都有用,但我注意到由于数据库初始化和预填充的异步处理导致出现了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;
	}
}

2 个答案:

答案 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上可用)很有用。