多任务应用中FDQuery与连接池的正确使用方法及访问冲突解决
问题修复方案
核心错误原因
你遇到的访问冲突主要来自三类不符合规范的实现:
- 多线程无锁访问VCL控件、共享日志对象、共享FDManager操作
- 连接池配置缺失必要参数,默认配置不符合多线程场景要求
- 实现逻辑不符合FireDAC官方多线程使用规范
具体修改步骤
1. 新增线程安全锁保护共享资源
首先在窗体私有变量中添加临界区,所有跨线程共享的资源访问都需要加锁:
private FCritical: TCriticalSection; FTargetTime: string; // 存储目标时间,避免线程直接访问VCL控件
在FormCreate中初始化临界区:
procedure TForm2.FormCreate(Sender: TObject); begin FCritical := TCriticalSection.Create; Log1 := TStringList.Create; Log2 := TStringList.Create; FDManager := TFDManager.Create(nil); FDManager.ResourceOptions.AutoReconnect := True; FDriverMySQL := TFDPhysMySQLDriverLink.Create(nil); FDriverSQLite := TFDPhysSQLiteDriverLink.Create(nil); // 提前初始化连接池,避免线程中并发创建 CreatePooledConnection; end;
在FormClose中释放临界区:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction); begin FCritical.Free; Log1.Free; Log2.Free; FDManager.Free; FDriverMySQL.Free; FDriverSQLite.Free; end;
2. 补充连接池必要配置
修改CreatePooledConnection方法,补充连接池核心参数,同时加锁避免并发创建冲突:
procedure TForm2.CreatePooledConnection; var oParams: TStrings; Conn:TStrings; Server, Database, UserName, Password: String; Port: Integer; begin // 填写实际数据库参数 Server := '你的数据库地址'; Database := '你的数据库名'; UserName := '你的用户名'; Password := '你的密码'; Port := 3306; FCritical.Enter; try oParams := TStringList.Create; Conn := TStringList.Create; try FDManager.GetConnectionDefNames(Conn); if Conn.IndexOf('Connection') = -1 then begin oParams.Add('Server='+Server); oParams.Add('Database='+Database); oParams.Add('User_Name='+UserName); oParams.Add('Password='+Password); oParams.Add('Port='+IntToStr(Port)); oParams.Add('DriverName='+FDriverMySQL.Name); // 连接池核心配置 oParams.Add('Pooled=True'); oParams.Add('Pool_MaximumItems=20'); // 最大连接数,可根据并发需求调整 oParams.Add('Pool_MinimumItems=2'); // 最小空闲连接数 oParams.Add('Pool_IdleTimeout=30000'); // 空闲连接30秒自动释放 oParams.Add('ConnectionLifeTime=300'); // 连接生命周期300秒,避免长连接失效 FDManager.AddConnectionDef('Connection', 'MySQL', oParams, True); FDManager.Open; end; finally oParams.Free; Conn.Free; end; finally FCritical.Leave; end; end;
3. 禁止跨线程直接访问VCL控件
修改BitBtn1Click,提前在主线程读取目标时间存储到变量,避免子线程访问VCL控件:
procedure TForm2.BitBtn1Click(Sender: TObject); begin // 主线程读取目标时间 FCritical.Enter; try FTargetTime := MaskEdit1.Text; finally FCritical.Leave; end; Ts1 := TTask.Create(procedure begin Test1; end); Ts1.Start; Ts2 := TTask.Create(procedure begin Test2; end); Ts2.Start; end;
线程逻辑中判断时间改为读取FTargetTime变量,所有日志操作加锁:
procedure TForm2.Test1; var fQuery : TFDQuery; HoraS: String; begin HoraS := FormatDateTime('hh:nn', TimeOf(NOW)); while HoraS <> FTargetTime do begin Sleep(10000); HoraS := FormatDateTime('hh:nn', TimeOf(NOW)); end; try FCritical.Enter; try Log1.Add('Creating TFDQuery'); finally FCritical.Leave; end; fQuery := CreateQuery; try FCritical.Enter; try Log1.Add('Adding SQL in TFDQuery'); finally FCritical.Leave; end; fQuery.SQL.Clear; fQuery.SQL.Add('SELECT id FROM table_two'); fQuery.SQL.Add('WHERE delete_flag IS NULL'); fQuery.SQL.Add('LIMIT 1'); FCritical.Enter; try Log1.Add('SQL of TFDQuery: ' + fQuery.SQL.Text); Log1.Add('Opening TFDQuery'); finally FCritical.Leave; end; fQuery.Open; FCritical.Enter; try Log1.Add('Result of TFDQuery: ' + fQuery.FieldByName('id').AsString); finally FCritical.Leave; end; finally fQuery.Free; end; except on E:Exception do begin FCritical.Enter; try Log1.Add('Error: ' + E.Message); finally FCritical.Leave; end; end; end; end;
Test2方法做相同的修改即可。
4. 优化TFDQuery创建逻辑
修改CreateQuery方法,添加连接自动释放配置,确保查询关闭后连接自动归还到连接池:
function TForm2.CreateQuery: TFDQuery; begin Result := TFDQuery.Create(nil); Result.FetchOptions.Mode := fmAll; Result.FetchOptions.RecordCountMode := cmFetched; Result.ResourceOptions.AutoReconnect := True; Result.ConnectionOptions.AutoClose := True; // 查询关闭后自动释放连接回池 Result.ConnectionName := 'Connection'; end;
额外注意
- 不要跨线程共享任何FireDAC的数据集、连接对象,每个线程的数据库对象要在线程内创建、线程内释放
- 示例中
BitBtn2Click里Ts1.Cancel;;多了一个冗余分号,可直接删除避免编译警告
内容的提问来源于stack exchange,提问作者Diego_F
相关产品推荐
相关产品推荐

