MobileServiceClient.PullAsync出现Safe handle has been closed错误
错误触发原因
- 从堆栈信息可定位到错误根源是Azure Mobile Services离线同步组件依赖的本地SQLite存储实例的安全句柄被提前销毁/释放,调用
PullAsync时尝试访问已失效的句柄触发异常。 - 仅未登录调用非授权接口时触发、登录后接口调用正常的核心逻辑是:
MobileServiceSyncContext(同步上下文)的初始化逻辑和Azure AD B2C登录状态做了耦合,未登录状态下初始化的SQLite存储实例被提前Dispose,登录完成后会重新创建有效的存储实例,因此登录后同步逻辑可以正常运行。 - 另一种可能的触发场景是未登录场景下存在多线程并发调用同步接口的情况,同一个SQLite存储实例被多个线程操作时,其中一个线程执行完成后释放了句柄,其他仍在运行的同步线程访问已释放的句柄抛出异常。
排查步骤
- 先定位到
OfflineSync.cs文件第1931行的SyncNonAuthAsync方法,检查调用PullAsync前MobileServiceSQLiteStore实例是否已完成初始化且未被释放,避免在同步逻辑执行完成前就销毁存储实例。 - 检查全局
MobileServiceClient、MobileServiceSyncContext的生命周期配置:不要给非授权接口和授权接口配置独立的同步上下文实例,全局复用同一个实例即可,避免实例切换时旧实例被释放导致的句柄失效。 - 在非授权接口的
PullAsync调用前增加状态校验:先判断MobileServiceSyncContext.IsInitialized是否为true,未初始化则先执行初始化逻辑再调用同步接口。 - 排查非授权场景下的同步逻辑是否存在并行执行:如果有多个同步任务同时运行,优先加锁限制串行执行,验证是否是并发操作导致的句柄提前释放。
解决方案
- 快速验证方案:清空应用本地缓存,或者卸载重装应用测试,排查是否是旧的本地SQLite存储文件损坏导致的句柄异常,如果测试后问题消失,后续可增加本地存储损坏时自动重建的逻辑。
- 代码修复方案1:调整同步上下文初始化时机,应用启动时就初始化全局唯一的
MobileServiceSQLiteStore和SyncContext实例,不要和登录状态耦合,无论是否登录都复用同一个实例,避免登录前后实例切换导致的释放问题。 - 代码修复方案2:在
SyncNonAuthAsync方法执行PullAsync前增加有效性校验,参考代码如下:
// 先校验同步上下文是否已初始化 if(!MobileService.SyncContext.IsInitialized) { var store = new MobileServiceSQLiteStore("localstore.db"); // 定义所有需要用到的表,包括非授权接口对应的表 store.DefineTable<YourNonAuthTable>(); await MobileService.SyncContext.InitializeAsync(store); } // 再执行PullAsync同步逻辑
- 代码修复方案3:如果存在并发同步场景,给非授权的同步逻辑加异步锁,避免同一时间多个同步任务操作存储实例,防止句柄被提前释放。
内容的提问来源于stack exchange,提问作者sidsud
相关产品推荐
相关产品推荐

