C# MAUI:DI上下文能否在AndroidX WorkManager与UI页面间共享?
MAUI中SQLite单例服务在UI与AndroidX WorkManager间共享问题解决
问题描述
在MAUI Android应用中,已将SQLite数据库服务注册为单例DI服务,但UI页面(主线程)和AndroidX WorkManager后台工作器获取的是不同的数据库服务实例,无法复用单例,导致无法有效控制SQLite的单写入者限制,存在并发写入损坏数据库的风险。核心需求是全应用共用同一数据库服务单例,确保数据库操作的准确性与写入控制。
现有代码
MauiProgram.cs
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder.Services.AddSingleton<Services.IDatabaseService, Services.DatabaseService>(); #if ANDROID builder.Services.AddSingleton<CustomWorkManagerFactory>(); builder.Services.AddSingleton<Platforms.Android.Sync.SyncWorker>(); #endif var app = builder.Build(); Helpers.ServiceLocator.ServiceProvider = builder.Services.BuildServiceProvider(); // 问题点:重复构建服务容器 #if ANDROID var context = Android.App.Application.Context; var customFactory = Helpers.ServiceLocator.ServiceProvider.GetRequiredService<Platforms.Android.Helpers.CustomWorkManagerFactory>(); var configuration = new AndroidX.Work.Configuration.Builder() .SetWorkerFactory(customFactory) .Build(); AndroidX.Work.WorkManager.Initialize(context, configuration); #endif return app; }
数据库服务写入逻辑
private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1); public async Task<int> InsertDB(object item) { await _writeSemaphore.WaitAsync(); try { int result = await Database().InsertAsync(item); return result; } catch (Exception ex) { throw; } finally { _writeSemaphore.Release(); } }
解决方案
1. 修复DI服务容器重复构建问题
代码中builder.Services.BuildServiceProvider()会创建新的独立服务容器,导致WorkManager和UI使用不同的DI上下文。应直接使用builder.Build()返回的MauiApp的ServiceProvider:
修改后的MauiProgram.cs关键部分:
var app = builder.Build(); Helpers.ServiceLocator.ServiceProvider = app.Services; // 使用已构建的主服务容器 #if ANDROID var context = Android.App.Application.Context; var customFactory = app.Services.GetRequiredService<Platforms.Android.Helpers.CustomWorkManagerFactory>(); // 从主容器获取实例 var configuration = new AndroidX.Work.Configuration.Builder() .SetWorkerFactory(customFactory) .Build(); AndroidX.Work.WorkManager.Initialize(context, configuration); #endif
2. 确保WorkManager与UI同进程运行
默认情况下WorkManager与APP主进程同进程,但如果在AndroidManifest.xml中为Worker配置了android:process属性,会导致WorkManager运行在独立进程,此时单例无法跨进程共享。检查并移除该配置:
移除类似如下的配置:
<service android:name=".Sync.SyncWorker" android:process=":sync_process" /> <!-- 这个属性会导致跨进程 -->
3. 正确实现自定义WorkManagerFactory
自定义Factory需从主DI容器获取Worker实例,确保Worker依赖的数据库服务是单例:
public class CustomWorkManagerFactory : WorkerFactory { private readonly IServiceProvider _serviceProvider; public CustomWorkManagerFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public override ListenableWorker CreateWorker( Context appContext, string workerClassName, WorkerParameters workerParameters) { var workerType = Type.GetType(workerClassName); if (workerType == typeof(SyncWorker)) { // 从主容器获取Worker实例,保证依赖的单例性 return _serviceProvider.GetRequiredService<SyncWorker>(); } // 其他Worker使用默认创建逻辑 return base.CreateWorker(appContext, workerClassName, workerParameters); } }
4. 跨进程场景的备选方案(若必须使用独立进程)
如果业务需求要求WorkManager运行在独立进程,单例共享不可行,可通过以下方式保证数据库安全:
- 启用SQLite WAL模式:WAL模式支持多进程安全读写,比传统的独占锁模式更适合跨进程场景。在数据库初始化时开启:
var db = new SQLiteAsyncConnection(dbPath); await db.ExecuteAsync("PRAGMA journal_mode=WAL;"); - 使用ContentProvider封装数据库访问:ContentProvider由Android系统管理,天然支持跨进程访问,可将所有数据库操作封装到ContentProvider中,确保统一的写入控制。
- 通过IPC机制转发数据库操作:后台Worker通过Messenger或AIDL与主进程通信,由主进程统一执行数据库操作,避免多进程直接操作数据库文件。
5. 强化写入控制
确保_writeSemaphore在数据库服务单例中是唯一实例,初始化时设置最大并发数为1:
private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1);
这样即使在多线程场景下,也能保证同一时间只有一个写入操作执行。
内容的提问来源于stack exchange,提问作者EdwinD
相关产品推荐
相关产品推荐

