SQLite 异步任务和等待运算符 - 任务会以正确的顺序执行吗?



我有一个SQLiteAsyncConnection数据库,其中包含多个表,我想在插入其中一个表后取回主键。我的插入在数据库模型中如下所示

public Task SaveZoneAsync(Zone zone)
{
if (zone.ID == 0)
{
return database.InsertAsync(zone);
}
else
{
return database.UpdateAsync(zone);
}
}

同样在数据库模型中,我有这种方法可以从给定表中获取最后插入的 id。

public async Task<int> GetLastInsertAsync(String tablename)
{
int x = await database.ExecuteScalarAsync<int>("SELECT last_insert_rowid() FROM " + tablename);
return x;
}

在我的 ViewModel 中,我有这种方法,并计划将 Zone 对象传递到其中以插入到数据库中

public static int AddZoneToDB(Zone zone)
{
Task AddZone = Task.Factory.StartNew(() => App.Database.SaveZoneAsync(zone));
AddZone.Wait();
Task<int> getID = App.Database.GetLastInsertAsync("Zone");
getID.RunSynchronously();
int zoneID = getID.Result;
return zoneID;
}

并像这样执行它

int zoneID = MapPageViewModel.AddZoneToDB(zoneToAdd);

我的问题是AddZoneToDB方法中的AddZone.Wait()是否会确保addZone任务在从getID.Result获取 int 之前完成运行?基本上试图确保我总是从正确的记录中获得正确的 ID。我只是不确定Wait()RunSynchronously()如何一起工作。这是 Xamarin.Forms 中的 Xamarin SQLite db。ID 设置为主键,并在区域模型中自动递增。

避免使用Task.Factory.StartNew,改用Task.Run。但是,由于目标成员已经返回了Task因此也确实不需要Task.Run

尝试使代码始终异步,而不是尝试混合异步和阻止代码。

public static async Task<int> AddZoneToDbAsync(Zone zone) {
await App.Database.SaveZoneAsync(zone);
int zoneID = await App.Database.GetLastInsertAsync("Zone");
return zoneID;
}

GetLastInsertAsync没有对局部变量执行任何操作,应该重构以仅返回任务。

public Task<int> GetLastInsertAsync(String tablename) {
return database.ExecuteScalarAsync<int>("SELECT last_insert_rowid() FROM " + tablename);
}

参考异步/等待 - 异步编程的最佳实践

这里有两个问题。

你要求的一个:在这种情况下我应该如何正确使用 async-await。还有一个,你没有提到的:你添加/更新一个元素并询问最后插入的区域的 ID。

您是否绝对确定在添加/更新和查询上次插入的 ID 之间,没有其他进程可能添加了区域?您想要刚刚添加/更新的区域的 ID,还是需要其他进程添加的区域的 ID?

为了确保您获得刚刚添加/更新的项目的 ID,明智的做法是让database.InsertAsyncdatabase.UpdateAsync返回完整的添加/更新区域(如实体框架(,或者至少返回添加/更新的 ID。

这些函数应如下所示:

async Task<int> InsertAsync(Zone zone)
{
.. // async do the insert; await until done
int zoneId = ... // get the Id of the inserted Zone
return zoneId;
}

如果您可以保证在插入区域和获取插入区域的 ID 之间没有其他人可以干扰,则可以保证您获得正确的 ID。

回到你关于异步等待的问题

Async-Await 通常用于您的线程必须等待另一个进程完成某些事情:写入文件、从数据库中检索数据、从 Internet 获取信息。而不是等待,你的线程可以做其他有用的事情。

在这次采访中,埃里克·利珀特(Eric Lippert(将此与厨师做早餐进行了比较。在中间的某个地方搜索异步等待。放好水壶烧开茶水后。他不会无所事事。相反,他开始切西红柿。当水沸腾时,他继续泡茶。

如果您的线程无事可做,那么启动一个新线程做一些事情是没有用的。此操作的开销将减慢该过程。我的建议是让你的线程使用 async-await 做所有事情。只有当你的线程必须做冗长的计算,而你的线程可以做其他有用的事情时,让另一个线程做这种冗长的计算才有用。但是,如果您的线程除了等待这种冗长计算的结果之外别无他法,请让您的线程执行此操作。

所以我的建议如下。使用上述调整后的 InsertAsync:

// Inserts / Updates Zone; returns the ID of the Saved zone
public async Task<int> SaveZoneAsync(Zone zone)
{
if (zone.ID == 0)
{
int zoneId = await database.InsertAsync(zone);
return zoneId;
}
else
{
await database.UpdateAsync(zone);
return zone.Id;
}
}
public static int AddZoneToDB(Zone zone)
{
Task<int> taskSaveZone = Task.Run( () => SaveZoneAsync(zone);
taskSaveZone.Wait();
int zoneId = taskSaveZone.Result;
return zoneId;
// TODO: consider putting this all in one statement
}

最新更新