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

SQLite Asynchronous Task and the Wait operator - will Tasks execute in correct order?

我有一个带有多个 table 的 SQLiteAsyncConnection 数据库,我想在插入其中一个数据库后取回主键。我的插入在数据库模型中看起来像这样

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

同样在数据库模型中,我有这种方法可以从给定的 table.

中获取最后插入的 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() 是否会在从 getID.Result 获取 int 之前确保 addZone 任务完成 运行 ?基本上是想确保我总是从正确的记录中获得正确的 ID。我只是不确定 Wait()RunSynchronously() 如何协同工作。这是 Xamarin.Forms 中的 Xamarin SQLite 数据库。 ID 设置为主键并在 Zone 模型中自动递增。

避免使用 Task.Factory.StartNew,而是使用 Task.Run。然而,由于目标成员已经 returning a 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 没有对局部变量做任何事情,应该重构为 return 任务。

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

引用Async/Await - Best Practices in Asynchronous Programming

这里有两个问题

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

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

为确保您获得刚添加/更新的项目的 ID,明智的做法是让 database.InsertAsyncdatabase.UpdateAsync return 完整添加/更新的区域(就像 entity framework 那样)或至少 return 添加/更新的 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 获取信息。您的线程可以做其他有用的事情,而不是等待。

In this interview, 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
}