如何锁定记录数组以在线程中添加和删除元素
Posted
技术标签:
【中文标题】如何锁定记录数组以在线程中添加和删除元素【英文标题】:How can lock array of records for adding and deleting element in thread 【发布时间】:2022-01-20 08:59:27 【问题描述】:我想一次又一次地从服务器获取一些查询,并在记录数组中列出这个 URL 查询。
为了释放内存,必须释放已处理的数组的元素。 此进程在线程上,以防止 App 冻结。 如果数组未锁定,则删除元素可能会引发异常,因为其他元素正在处理中,或者添加或删除元素的索引已更改。
我的问题是:'?'
为了理解我的实际应用代码,简化了这个示例代码:
uses IdHTTP;
type
tMyRecAra=record
sta:integer;
url:string;
// ...
// ...
end;
var MyRecAra: array of tMyRecAra;
procedure TfoTestAra.btAddClick(Sender: TObject);
var cou:integer;
begin
//+ start lock array MyRecAra ?
cou:=length(MyRecAra);
SetLength(MyRecAra, length(MyRecAra)+1);
MyRecAra[cou].sta:=0;
MyRecAra[cou].url:='http:/......';
//- stop lock array MyRecAra ?
end;
procedure TfoTestAra.btStartClick(Sender: TObject);
var
IdHTTP1:TIdHTTP;
mainThr,curThr : TThread;
cStream:TMemoryStream;
begin
mainThr := TThread.CreateAnonymousThread(
procedure
begin
while true or other condition do
begin
curThr := TThread.CreateAnonymousThread(
procedure
var i:integer;
begin
//+ start lock array MyRecAra ?
for i := 0 to (length(MyRecAra)-1) do
begin
if (MyRecAra[i].sta=0) then
begin
MyRecAra[i].sta:=1;
//...
//..
for example : IdHTTP1.Get(MyRecAra[i].url,cStream);
//...
//..
end;
end;
//- stop lock array MyRecAra ?
end);
curThr.Start;
sleep(5000);
end;
end);
mainThr.start;
end;
procedure TfoTestAra.Timer1Timer(Sender: TObject);
var
sumFee:integer;
i, j:integer;
begin
// timer.interval=10000;
//+ start lock array MyRecAra?
sumFee:=0;
for i := 0 to (length(MyRecAra)-1) do
begin
if (MyRecAra[i].sta=1) then
begin
inc(sumFee);
for j := (i+1) to sumFee-1 do
begin
if (MyRecAra[j].sta <> 1) then
MyRecAra[i]:=MyRecAra[j]
end;
end;
end;
if sumFee<>0 then
SetLength(MyRecAra, (length(MyRecAra)-sumFee));
//+ stop lock array MyRecAra ?
end;
End.
【问题讨论】:
@Dalija Prasnikar。您的解决方案对于需要锁定和解锁内存的其他人很有用,并且它在我的简单代码上完美运行,但我的主要代码非常复杂,应用此方法需要对其进行大量更改。请再次附上您的学习答案,供他人参考。 当你准备好之后,你应该再问一个问题。在这里看起来绝对不正确的一件事是对 Timer 进行清理。 【参考方案1】:您可以使用锁来保护对共享数据的访问,然后在您访问数据的所有地方的一般模式是:
Lock.Enter;
try
// procected code
finally
Lock.Leave;
end;
您需要在与需要保护的数据相同的范围内声明锁变量,并且您需要在第一次使用该锁之前对其进行初始化,并在不再需要时将其释放。
例如,如果你的MyRecAra
是单元中的全局数据,那么Lock
也需要是全局的,在单元的初始化部分初始化,在终结部分释放。
如果MyRecAra
是表单或其他类中的字段,那么Lock
也将是该类中的字段,在构造函数中初始化并在析构函数中释放。
常用的锁是TCriticalSection
。还有其他类型的锁,但首先这个就可以了。
var
Lock: TCriticalSection;
MyRecAra: TMyRecAra;
initialization
Lock := TCriticalSection.Create;
finalization
Lock.Free;
end.
【讨论】:
谢谢。我将您的方法添加到我的示例代码中,它工作正常,但是当我将这种方式添加到主代码中时,我的应用程序挂起并且在跟踪它时,我看到'lock.enter'在线程内发生了很多次,然后' lock.leave' 一个接一个地发生,最后在 'lock.enter' 行之一上跟踪停止。提到我用这个结构封装了我的所有代码:'try....lock.enter...finally...lock.leave...end'Enter
必须在try
之前,如 Dalija 的回答所示。确保每个成功的Enter
都有对应的Leave
。
除了 Remy 所说的之外,您还有使用锁的模板,但这并不意味着您会很轻松。您正在从后台线程和主线程请求锁定。如果您在后台线程中长时间持有锁,则主线程将被冻结,直到它设法获取锁,反之亦然。有多种方法可以解决此类问题,但解决方案很大程度上取决于您要完成的工作。
像一个简单的网络应用程序(例如 WhatsApp),我想从服务器上的数据库请求查询,http 协议的基础,在从服务器接收到结果后,继续分析接收到的数据并影响我的应用程序库那个结果。这个请求保存到一个数组中,然后一个一个地进行。您的解决方案非常有用,但是在我的应用程序上,有很多分散并应用您的建议(防止嵌套线程和使用正确的 try...finally...end ),我无法阻止我的应用程序挂起。
你需要改变你的完整代码逻辑。但是关于你在做什么有太多的未知数,无法为你提供如何处理它的准确答案。例如,您在计时器中做什么 - 您是否在刷新数据,如果是这样,那么这是错误的方法,您应该在计时器上而不是在实际准备好新数据时从后台线程发出刷新信号。从服务器检索结果时是否需要保持阵列锁定。更好的选择是在一些临时存储中检索结果,并且仅在添加新数据时锁定数组。以上是关于如何锁定记录数组以在线程中添加和删除元素的主要内容,如果未能解决你的问题,请参考以下文章