flutter 是单线程架构,按道理理说,flutter 不会出现 java 的多线程相关的问题。
但在我使用 flutter 过程中,却发现 flutter 依然会存在数据操作原子性的问题。
其实 flutter 中存在多线程的(isolate 隔离池),只是 flutter 中的多线程更像 java 中的多进程,因为 flutter 中线程不能像 java 一样,可以两个线程去操作同一个对象。
我们一般将计算任务放在 flutter 单独的线程中,例如一大段 json 数据的解析,可以将解析计算放在单独的线程中,然后将解析完后的 map<string, dynamic> 返回到主线程来用。
在 java 中,我们一般喜欢用单例模式来理解 java 多线程问题。这里我们也自学编程入门教程以单例来举例,我们先来一个正常的:
由于 flutter 是单线程架构的, 所以上述代码是没有问题的。
但是, 和 java 不同的是, flutter 中存在异步方法。
做 app会计实习周记范文 开发肯定会涉及到数据持久化,android 开发应该都熟悉 sharedpreferences,flutter 中也存在 sharedpreferences 库,我们就以此来举例。同样实现单例模式,只是这次无可避免的需要使用 flutter 中的异步:
运行上面的代码,打印日志如下:
instance1.hashcode = 428834223
instance2.hashcode = 324692380
可以发现,我们两次调用 spsingleton.getinstance() 方法,分别创建了两个对象,说明上面的单例模式实现有问题。
我们来分析一下 getinstance() 龙岗电大方法:
当第一次调用 getinstance() 方法时,代码在运行到 1 处时,发现 _instance 为 null, 就会进入 if 语句里面执行 2 处, 并因为 await 关键字挂起, 并交出代码的执行权, 直到被 await 的 future 执行完毕,最后将创建的 spsingleton 对象赋值给 _instance 并返回。
当第二次调用 getinstance() 方法时,代码在运行到 1 处时,可能会发现 _instance 还是为 null (因为 await spsingleton._fromsharedpreferences() 需要 10ms 才能返回结果), 然后和第一次调用 getinstance() 方法类似, 创建新的 spsingleton 对象赋值给 _instance。
最后导致两次调用 getinstance() 方法, 分别创建了两个对象。
问题原因知道了,那么该怎样解决这个问题呢?
究其本质,就是 getinstance() 方法的执行不具有原子性,即:在一次 getinstance() 方法执行结束前,不能执行下一次 getinstance() 方法。
幸运的是, 我们可以借助 completer 来将异步操作原子化,下面是借助 completer 改造后的代码:
我们再次分析一下 getinstance() 方法:
当第一次调用 getinstance() 方法时, 1 处和 2 处都会判定为 true, 然后进入执行到 3 处创建一个的 completer 对象, 然后在 4 的 await 处挂起, 并交出代码的执行权, 直到被 await 的 future 执行完毕。
此时第二次调用的 getinstance() 方法开始执行,1 处同样会判定为 true, 但是到 2 处时会判定为 fal, 从而进入到 el, 并因为 6 处的 await 挂起, 并交出代码的执行权;
此时, 第一次调用 getinstance() 时的 4 处执行完毕, 并执行到 5, 并通过 completer 通知第二次调用的 getinstance() 方法可以等待获取代码执行权了。
最后,两次调用 getinstance() 方法都会返回同一个 spsingleton 对象,以下是打印日志:
instance1.hashcode = 786567983
instance2.hashcode = 786567983
由于 flutter 的 future 是支持多次 await 的, 所以即便是连续 n 次调用 getinstance() 方法, 从第 2 到 n 次调用会 await 同一个 completer.future, 最后也能返回同一个对象。
虽然我们经常拿单例模式来解释说明 java 多线程问题,可这并不代表着 java 只有在单例模式时才有多线程问题。
同样的,也并不代表着 flutter 只有在单例模式下才有原子操作问题。
我们同样以数据持久化来举例,只是这次我们以数据库操作来举例。
我们在操作数据库时,经常会有这样的需求:如果数据库表中存在这条数据,就更新这条数据,否则就插入这条数据。
为了实现这样的需求,我们可能会先从数据库表中查询数据,查询到了就更新,没查询到就插入,代码如下:华东五市是哪五市
我们期望的输出日志为:
执行了插入
执行了更新
但不幸的是, 输出的日志为:
执行了插入
执行了插入
原因也是异步方法操作数据, 不是原子操作, 导致逻辑异常。
也许我们也可以效仿单例模式的实现,利用 completer 将 inrtorupdate() 方法原子化。
但对于数据库操作是不合适的,因为我们可能还有其它需求,比如说:调用插入数据的方法,然后立即从数据库中查询这条数据,发现找不到。
如果强行使用 completer,那么到最后,可能这个类中会出现一大堆的 completer ,代码难以维护。
其实我们想要的效果是,当有异步方法在操作数据库时,别的操作数据的异步方法应该阻塞住,也就是同一时间只能有一个方法来操作数据库。我们其实可以使用任务队列来实现数据库操作的需求。
我这里利用 completer 实现了一个任务队列:
由于 flutter 中的 future 不支持暂停操作, 一旦开始执行, 就只能等待执行完。
所以这里的任务队列实现是基于方法的延迟调用来实现的。
taskqueue 的用法示例如下:
值得注意的是: 这里的 taskqueue 不支持 submit await submit, 原因及示例代码已在注释中说明,这里不再赘述。
为了避免出现 submit await submit 的情况,我代码注释中也做出了建议(假设当前类为 dbhelper):
将数据库的增、删、查、改操作封装成私有的异步方法, 且私有异步方法不能使用 submit;
dbhelper 的公有方法, 可以调用自己的私有异步方法, 但不能调用自己的公有异步方法, 公有异步方法可以使用 submit;
这样就不会出现 submit await submit 的情况了。
房屋买卖合同注意事项于是,上述的数据库操作示例代码就变成了以下的样子:
输出日志也变成了我们期望的样子:
执行了插入
执行了更新
flutter 异步方法修改数据时, 一定要注意数据操作的原子性, 不能因为 flutter 是单线程架构,就忽略多个异步方法竞争导致数据异常的问题。
flutter 保证数据操作的原子性,也有可行办法,当逻辑比较简单时,可直接使用 completer,当逻辑比较复杂时,可以考虑使用任务队列。
另外,本文中的任务队列实现有很大的缺陷,不支持 submit await submit,否则整个任务队列会被阻塞住。
到此这篇关于flutter如何保证数据操作原子性的文章就介绍到这了,更多相关flutter数据操作原子性内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!
本文发布于:2023-04-06 01:10:47,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/a565c95c9f4c23c5df29c6762fd35175.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:Flutter如何保证数据操作原子性详解.doc
本文 PDF 下载地址:Flutter如何保证数据操作原子性详解.pdf
留言与评论(共有 0 条评论) |