FireMonkey框架线程使用疑问:无需Synchronize为何可行?
问题解答
为什么不用TThread.Synchronize也能运行?
这只是巧合,属于未定义行为。FMX/VCL的UI控件并非线程安全,跨线程直接操作UI(比如你的代码里向cb_object_list添加项、调用Edit1.AssignItems)本质上违反框架规则。
之所以暂时没报错,可能是因为线程执行时UI线程刚好处于空闲状态,或者控件内部数据结构暂时没被并发操作破坏,但这种情况完全不可靠——随时可能出现程序崩溃、控件显示异常、数据错乱等问题,只是你还没碰到而已。
这种实现方式是否正确?
完全不正确。正确的做法是把所有UI相关操作放到主线程执行,通过TThread.Synchronize或TThread.Queue实现线程同步:
修改UpdateBuildingList的代码如下:
procedure TFrameSystems.UpdateBuildingList; begin TThread.CreateAnonymousThread(procedure var s1: TStrings; JSONObject: TJSONObject; i: integer; // 临时存储UI数据,避免跨线程访问控件和共享对象 comboItems: array of TPair<string, TKey>; editItems: array of TPair<string, TIntObj>; begin // 1. 后台线程处理耗时操作:调用服务、更新用户数据 JSONObject := ClientModule.ServerMethodsClient.RefreshUserBuildingList(ActiveUser.id); ActiveUser.RefreshBuildingList(JSONObject); // 2. 复制数据到线程本地变量,隔离共享资源 SetLength(comboItems, ActiveUser.BuldingList.Count); SetLength(editItems, ActiveUser.BuldingList.Count); for I := 0 to ActiveUser.BuldingList.Count - 1 do begin comboItems[I] := TPair<string, TKey>.Create(ActiveUser.BuldingList[I].Name, TKey.Create(IntToStr(ActiveUser.BuldingList[I].id))); editItems[I] := TPair<string, TIntObj>.Create(ActiveUser.BuldingList[I].Kks, TIntObj.Create(ActiveUser.BuldingList[I].id)); end; s1 := TStringList.Create; for I := 0 to High(editItems) do begin s1.AddObject(editItems[I].Key, editItems[I].Value); end; // 3. 同步到主线程更新UI TThread.Synchronize(nil, procedure begin for I := 0 to High(comboItems) do begin cb_object_list.Items.AddObject(comboItems[I].Key, comboItems[I].Value); end; Edit1.AssignItems(s1); s1.Free; end); end).Start; end;
补充说明:
TThread.Synchronize会阻塞后台线程,直到主线程完成UI操作;若不想阻塞后台线程,可改用TThread.Queue,它会把UI操作放入主线程消息队列,后台线程继续执行。- 尽量在同步块外完成数据准备,减少对共享对象(如
ActiveUser)的跨线程访问,降低冲突风险。
内容的提问来源于stack exchange,提问作者Oceangrave
相关产品推荐
相关产品推荐

