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

时间:2018-07-07 16:24:17

标签: c# xamarin async-await task sqlite-net

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

RaisePostBackEvent

在数据库模型中,我也使用这种方法从给定表中获取最后插入的ID。

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

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

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

并像这样执行它

    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); 获取int之前,AddZone.Wait()方法中的AddZoneToDB是否会确保addZone任务已完成运行?基本上是尝试确保我总是从正确的记录中获得正确的ID。我只是不确定getID.ResultWait()如何一起工作。这是Xamarin.Forms中的Xamarin SQLite数据库。 ID被设置为主键,并在区域模型中自动递增。

2 个答案:

答案 0 :(得分:2)

避免使用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 - Best Practices in Asynchronous Programming

答案 1 :(得分:1)

这里有两个问题。

您要求的:在这种情况下,我应该如何正确使用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。

回到您有关异步等待的问题

异步等待通常在线程必须等待另一个进程完成某些事情时使用:写入文件,从数据库检索数据,从Internet提取信息。不必等待,线程可以做其他有用的事情。

In this interview, Eric Lippert将其与做早餐的厨师进行了比较。在中间的某个地方搜索async-await。放好水壶后把茶水烧开。他不会袖手旁观。相反,他开始切西红柿。水烧开后,他继续泡茶。

如果您的线程无事可做,则没有必要启动一个新线程。这样做的开销将减慢该过程。我的建议是让您的线程使用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
}