【问题标题】:SQLite Asynchronous Task and the Wait operator - will Tasks execute in correct order?SQLite 异步任务和等待运算符 - 任务会以正确的顺序执行吗?
【发布时间】:2018-12-15 22:07:02
【问题描述】:

我有一个包含多个表的 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 设置为主键并在 Zone 模型中自动递增。

【问题讨论】:

  • 试试看吧。也可以尝试使用 async/await,因为它更容易
  • 我一直无法理解如何很好地使用 async/await。你可以看到我在代码中使用了一点,但我厌倦了必须做这么多工作来弄清楚如何使方法异步等才能使用 await。如果您有任何关于如何使用 await/async 的非常好的教程,您可以指出我将不胜感激。我在网上找到的文档似乎让我更加困惑。
  • 我建议使用 async/await consistently 或者将连接类型更改为 SQLiteConnection 并使用同步代码并将其包装在线程中(通过 task.run 或手动创建的线程实例)

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


【解决方案1】:

避免使用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

【讨论】:

  • 谢谢恩科西。这不仅对我的项目有所帮助。您实际上帮助我更好地理解了如何异步编码。我没有意识到 await 运算符允许您获取 TResult 而不是 Task。教自己这个,很难学。非常感谢。
  • @TravisFleenor 很高兴为您提供帮助。你会觉得这篇文章很有趣参考Async/Await - Best Practices in Asynchronous Programming
【解决方案2】:

这里有两个问题。

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

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
}

【讨论】:

  • 谢谢哈罗德!期待我回家后仔细看看这个。尤其是您关于在插入/更新方法中获取区域 ID 的建议非常有见地且很有帮助。虽然在这个本地 SQLite 数据库上,一旦我们的 API 完成并且我们实际上正在连接到远程数据库以更新区域表,其他人就不可能进入区域,另一个用户可能会同时进入区域将非常存在。有一种方法来防止该表上的并发更新出现任何问题将非常重要。这是一个很好的建议。
  • 你确定你的程序不能启动两次吗?
  • 是的。只有一个应用程序实例将运行。这是一个基于 Xamarin 的移动应用程序。当我尝试重新打开它时,它只会回到现有实例。
猜你喜欢
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-07
  • 2012-11-15
  • 2014-03-18
  • 2017-08-30
  • 1970-01-01
相关资源
最近更新 更多