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(有时)在更新后获取错误结果的主要内容,如果未能解决你的问题,请参考以下文章
无法在 UWP 中设置 SQLite 对象 - Xamarin Forms
如何在 Xamarin Forms 中将日期、日期时间和时间保存到 SQLite 数据库?