Xamarin.Forms Sqlite(有时)在更新后获取错误结果

Posted

技术标签:

【中文标题】Xamarin.Forms Sqlite(有时)在更新后获取错误结果【英文标题】:Xamarin.Forms Sqlite (sometimes) fetching bad result after update 【发布时间】:2022-01-12 20:37:56 【问题描述】:

我有一个 Xamarin.Forms 项目,它使用来自 sqlite-net-pcl nuget 包(版本 1.2.0)的本地数据库文件 Sqlite(该文件称为 datas.lite)。

我有一个名为 Item 的表:

public class Item

    [PrimaryKey]
    public int ID  get; set; 
    public bool IsActive  get; set; 

还有一个带有静态连接 SQLite 的存储库 ItemRepository,我用来更新 Item 行:

public class ItemRepository

   private static SQLite.SQLiteConnection _conn;
   private static SQLite.SQLiteConnection Conn
   
          get
          
              if (_conn == null)
                  _conn = new SQLite.SQLiteConnection("myPath/datas.lite");
              return _conn
          
   
   public ItemRepository()
   
       Conn.CreateTable<Item>();
   
   public Item GetById(int id)
   
       return Conn.Get<Item>(id);
   
   // Retrieves all items from table Item
   public IEnumerable<Item> GetAll()
   
        return Conn.Table<Item>();
   
   
   // Updates the provided item
   public int InsertOrReplace(Item item)
   
        return Conn.InsertOrReplace(item, typeof(Item));
   

应用通过更新 Item 表每 5 分钟修改一次所有项目的 IsActive 属性(方法 TimerHelper.StartTimerInNewThread 在启动时调用一次)。

TimerHelper.StartTimerInNewThread(TimeSpan.FromSeconds(delais), () 
            =>
            
                try
                 
                     // retrieve all items with DB
                     List<Item> items = repo.GetAll();
                     foreach (Item item in items)
                     
                          item.IsActive = !item.IsActive;
                          if (repo.InsertOrReplace(item) == 1)
                          
                               Log?.Info($"Item item has been updated in DB: IsActive = repo.GetItem(item).IsActive.");
                          
                          else
                          
                              throw new Exception($"InsertOrReplace() method returned a value != 1.");
                          
                    
                
                catch (Exception ex)
                
                    // Log exception here
                
            );



            

在更新表 Item 的行后,我立即检查(我记录每个 Item 的每个 IsActive 属性值)所有项目的 IsActive 属性是否实际更改。到目前为止,一切都很好。 但是如果我让应用程序运行几个小时,有时,检查不会反映以前的更新......例如,应用程序将所有项目的 IsActive 属性设置为 TRUE,但对 IsActive 属性的立即请求返回 FALSE所有项目。

如果我通过 DbBrowser for Sqlite 从数据库本地文件 (datas.lite) 中读取表项,则每个项的 IsActive 属性都设置为 TRUE,这是正确的。那么为什么我在更新后立即发出的读取请求对所有项目都返回了 FALSE,sqlite 是否有任何处于活动状态的缓存?或者是因为我有一个静态连接,而且它永远不会关闭,(而且根据微软文档,这是推荐的做法:https://docs.microsoft.com/en-us/xamarin/get-started/quickstarts/database?pivots=windows)

感谢您的帮助

【问题讨论】:

请在您的问题中添加所有相关代码。否则,人们只是在猜测可能出了什么问题。在没有看到任何代码的情况下,我的第一个猜测是在属性更新和检查它们的值之间存在某种“竞争条件”。我不确定幕后发生了什么,但一般原则 - 与 sqlite 无关,但确保应用程序中的所有内容都有机会完成其工作 - 是 queue 代码稍后运行,也许在特定线程上运行。但正确地做到这一点取决于确切的代码细节。 “我的应用程序通过更新 Item 表每 5 分钟修改一次所有项目的 IsActive 属性。在更新表 Item 的行后,我立即检查(我记录每个 IsActive 属性值每个项目),所有项目的 IsActive 属性实际上都发生了变化。“ - 这是要显示的代码。包括任何启动它的东西。它是否在计时器上运行? “我为每个 Item 记录每个 IsActive 属性值” - 是否有一些东西在运行时变得越来越大?这是一个“随着时间的推移变得更糟”的问题吗?或者它何时失败似乎是完全随机的? 如果是由于“缓存”,您会立即看到它。这不会是一个不经常发生的随机问题。要么应用程序随着时间的推移“陷入困境”,以至于一些没有立即导致问题的时间问题开始越来越多地暴露出来,或者它一直存在,但概率很低。 谢谢@ToolmakerSteve,我更新了我的帖子,以提供更多信息和代码。它实际上是在计时器中运行的,我认为您是对的,它不是来自 Sqlite 缓存,否则,我会在第一次更新后立即看到问题。但问题实际上是随机出现的。感谢您的任何建议 【参考方案1】:

鉴于它在随机时间后停止正常运行,您需要保护计时器代码在其仍在运行时不被重新输入。

比较这三种使用计时器重复运行代码的方式。

这是“典型的”定时器代码。 “秒”是完成工作的时间间隔。如果代码 always 在计时器延迟再次触发之前完成,则可以正常工作。如果计时器代码可能占用整个时间间隔,则会出现问题。 (或者如果某些后台工作,例如 GC,花费了足够的时间,您的计时器代码会吃掉剩余的时间。)如果计时器代码花费的时间太长以至于计时器事件开始“堆积”,从而使您的应用程序的其余部分无法获得任何时间。

错误的计时器代码

using Timer = System.Timers.Timer;

    private void StartFixedDelayTimer(float seconds, Action action)
    
        _timer = new Timer(1000 * seconds);
        // Repeat the timer event until explicitly stopped. (true by default).
        _timer.AutoReset = true;
        _timer.Elapsed += (sender, e) => action();
        _timer.Start();
    

良好的计时器代码

这很相似,但如果工作仍在完成,它会通过“跳过”工作来保护自己:

    // For long running "action", increase "idleSeconds" to guarantee more time for other background tasks.
    private void StartFixedDelayTimerAvoidReentrancy(float seconds, Action action, float idleSeconds = 0.1f)
    
        _timer = new Timer(1000 * seconds);
        // Repeat the timer event until explicitly stopped. (true by default).
        _timer.AutoReset = true;

        bool entered = false;
        _timer.Elapsed += (sender, e) => 
            if (entered)
                // Timer code already running! Skip this one.
                return;
            entered = true;
            try 
                action();
                // IMPORTANT: This is needed to "see and skip" next timer event,
                // if it happens during "action". Without this, timer events can "pile up",
                // starving other background tasks.
                System.Threading.Thread.Sleep((int)(1000 * idleSeconds));
             finally 
                entered = false;
            
        ;

        _timer.Start();
    

这是另一种方法。它不要求再次完成工作,直到第一次完成之后。这里的“秒”是完成工作和开始下一个工作之间的时间量。如果您不需要按照严格的时钟时间表完成工作,这很有用。它的优点是,无论“操作”需要多长时间,您的应用程序代码的其余部分都会在重新开始这项工作之前获得“秒”的 CPU 时间 - 不会“饿死”您的应用程序。

    private void StartDelayBetweenWorkTimer(float seconds, Action action)
    
        _timer = new Timer(1000 * seconds);
        // Only fire the timer once. (But in Elapsed, we fire it again.)
        _timer.AutoReset = false;
        _timer.Elapsed += (sender, e) => 
            action();
            // Fire the timer again. Work will be done again "seconds" seconds after this statement is called.
            _timer.Start();
        ;
        _timer.Start();
    

【讨论】:

以上是关于Xamarin.Forms Sqlite(有时)在更新后获取错误结果的主要内容,如果未能解决你的问题,请参考以下文章

如何在Xamarin Forms中使用SqLite?

无法在 UWP 中设置 SQLite 对象 - Xamarin Forms

Xamarin Forms Sqlite 创建任务

如何在 Xamarin Forms 中将日期、日期时间和时间保存到 SQLite 数据库?

Xamarin.Forms 中的 sqlite 如何更新本地数据库中的单个记录

异步事件处理程序有时会挂在 Xamarin.Forms