已经有一段时间了,我正在努力确保这个类有线程。 不幸的是,我无法理解在哪里采取适当的行动。
问题:
保存过程需要很长时间,因此我想在专用线程中使用它。 我们的想法是,如果线程保存正在运行,则根节点中的每个更改都会增加保存的需求。如果存在请求,则保存线程结束,然后重新保存仍然将请求置零,直到没有请求为止。
但是在我添加节点之前,没有问题。但是,目前要移除节点(然后是所有子节点),如果正在运行线程保存,则会创建正确的异常。
有一种解决方案或其他方法可以在保存根节点的同时使其可编辑。 显然没有为每个保存请求复制整个节点?
谢谢大家。
基类:
interface
uses
System.Classes, System.SysUtils;
type
TNode<T> = class
public type
TNodeForEachProc = reference to procedure( const ANode: TNode<T>; var ABreak: Boolean );
TNodeWriteProc = reference to procedure( const AWriter: TBinaryWriter; const AData: T );
TNodeReadProc = reference to function( const AReader: TBinaryReader ): T;
TNodeBufferedWriteProc = reference to procedure( const AWriter: TBufferedWriter; const AData: T );
TNodeBufferedReadProc = reference to function( const AReader: TBufferedReader ): T;
TNodeProgressProc = reference to procedure( const AIndex, ACount: Integer );
private
FCount: Integer;
FCapacity: Integer;
FNodes: TArray<TNode<T>>;
{$IFDEF AUTOREFCOUNT}[Weak]{$ENDIF}FParent: TNode<T>;
procedure Grow;
procedure SetCapacity( const AValue: Integer );
function Get( AIndex: Integer ): TNode<T>; inline;
function GetIndex: Integer;
function GetLevel: Integer;
procedure Adopt( const ANode: TNode<T> );
procedure Orphan;
procedure SetParent( const AValue: TNode<T> );
protected
property Nodes:TArray<TNode<T>> read FNodes;
public
Data: T;
constructor Create( const ACapacity: Integer = 0 ); overload;
constructor Create( const ANode: TNode<T> ); overload;
destructor Destroy; override;
procedure Assign( const ANode: TNode<T> );
function Add( const AData: T ): TNode<T>;
procedure Clear; inline;
function Count( const ARecursive: Boolean = False ): Integer; inline;
function Empty: Boolean; inline;
procedure Delete( const AIndex: Integer; const ACount: Integer = 1 );
function ForEach( const AProc: TNodeForEachProc; const ARecursive: Boolean = True ): Boolean;
property Index: Integer read GetIndex;
property Node[ Index: Integer ]: TNode<T> read Get; default;
property Level: Integer read GetLevel;
property Parent: TNode<T> read FParent write SetParent;
procedure SaveToStream( const AStream: TStream; const AWriterProc: TNodeWriteProc; const AProgressProc: TNodeProgressProc = nil );
procedure LoadFromStream( const AStream: TStream; const AReaderProc: TNodeReadProc; const AProgressProc: TNodeProgressProc = nil );
procedure SaveToFile( const AFileName: TFileName; const AWriterProc: TNodeBufferedWriteProc; const AProgressProc: TNodeProgressProc = nil );
procedure LoadFromFile( const AFileName: TFileName; const AReaderProc: TNodeBufferedReadProc; const AProgressProc: TNodeProgressProc = nil );
end;
implementation
{ TNode<T> }
constructor TNode<T>.Create( const ACapacity: Integer = 0 );
begin
SetCapacity( ACapacity );
end;
constructor TNode<T>.Create( const ANode: TNode<T> );
begin
Assign( ANode );
end;
destructor TNode<T>.Destroy;
begin
Clear;
Orphan;
inherited;
end;
procedure TNode<T>.Assign( const ANode: TNode<T> );
var
VIndex: Integer;
begin
Clear;
Data := ANode.Data;
FCount := ANode.FCount;
SetLength( FNodes, FCount );
for VIndex := 0 to FCount - 1 do
begin
FNodes[ VIndex ] := TNode<T>.Create;
FNodes[ VIndex ].Assign( ANode.FNodes[ VIndex ] );
end;
end;
function TNode<T>.Add( const AData: T ): TNode<T>;
begin
Result := TNode<T>.Create;
Result.Data := AData;
Adopt( Result );
end;
procedure TNode<T>.Clear;
begin
Delete( 0, FCount );
FNodes := nil;
FCount := 0;
FCapacity := 0;
end;
function TNode<T>.Count( const ARecursive: Boolean = False ): Integer;
var
VCount: Integer;
begin
if ARecursive then
begin
VCount := 0;
ForEach(
procedure( const Node: TNode<T>; var ABreak: Boolean )
begin
Inc( VCount );
end
);
Result := VCount;
end else
Result := FCount;
end;
procedure TNode<T>.Delete( const AIndex: Integer; const ACount: Integer = 1 );
var
VIndex: Integer;
begin
for VIndex := AIndex to AIndex + ACount - 1 do
begin
FNodes[ VIndex ].FParent := nil;
FNodes[ VIndex ].DisposeOf;
Dec( FCount );
end;
System.Delete( FNodes, AIndex, ACount );
end;
function TNode<T>.Empty: Boolean;
begin
Result := FCount = 0;
end;
function TNode<T>.ForEach( const AProc: TNodeForEachProc; const ARecursive: Boolean = True ): Boolean;
type
TTypeNode = TNode<T>;
var
VIndex: Integer;
VTypeNode: TTypeNode;
VBreak: Boolean;
begin
Result := True;
if Assigned( AProc ) then
for VIndex := 0 to Count - 1 do
begin
VTypeNode := FNodes[ VIndex ];
VBreak := False;
AProc( VTypeNode, VBreak );
if VBreak then
Exit( False );
if ARecursive then
if not VTypeNode.ForEach( AProc ) then
Exit( False );
end;
end;
procedure TNode<T>.Grow;
var
VDelta: Integer;
begin
if FCapacity > 64 then
VDelta := FCapacity div 4
else
if FCapacity > 8 then
VDelta := 16
else
VDelta := 4;
SetCapacity( FCapacity + VDelta );
end;
procedure TNode<T>.SetCapacity( const AValue: Integer );
begin
if AValue > FCapacity then
begin
SetLength( FNodes, AValue );
FCapacity := AValue;
end;
end;
function TNode<T>.Get( AIndex: Integer ): TNode<T>;
begin
Result := FNodes[ AIndex ];
end;
function TNode<T>.GetIndex: Integer;
var
VIndex: Integer;
begin
if FParent <> nil then
for VIndex := 0 to FParent.Count - 1 do
if FParent[ VIndex ] = Self then
Exit( VIndex );
Result := - 1;
end;
function TNode<T>.GetLevel: Integer;
begin
if FParent = nil then
Result := 0
else
Result := FParent.Level + 1;
end;
procedure TNode<T>.Adopt( const ANode: TNode<T> );
var
VIndex: Integer;
begin
ANode.FParent := Self;
VIndex := FCount;
if VIndex = FCapacity then
Grow;
FNodes[ VIndex ] := ANode;
Inc( FCount );
end;
procedure TNode<T>.Orphan;
begin
if FParent <> nil then
begin
System.Delete( FParent.FNodes, Index, 1 );
Dec( FParent.FCount );
end;
end;
procedure TNode<T>.SetParent( const AValue: TNode<T> );
begin
if FParent <> AValue then
begin
Orphan;
FParent := AValue;
if FParent <> nil then
FParent.Adopt( Self );
end;
end;
procedure TNode<T>.SaveToStream( const AStream: TStream; const AWriterProc: TNodeWriteProc; const AProgressProc: TNodeProgressProc = nil );
var
VCount: Integer;
VIndex: Integer;
VWriter: TBinaryWriter;
begin
if Assigned( AWriterProc ) then
begin
VCount := Count( True );
VIndex := 0;
VWriter := TBinaryWriter.Create( AStream );
try
VWriter.Write( VCount );
AWriterProc( VWriter, Data );
VWriter.Write( Count );
ForEach(
procedure( const ANode: TNode<T>; var ABreak: Boolean )
begin
AWriterProc( VWriter, ANode.Data );
VWriter.Write( ANode.Count );
if Assigned( AProgressProc ) then
begin
AProgressProc( VIndex, VCount );
Inc( VIndex );
end;
end
);
finally
VWriter.DisposeOf;
end;
end;
end;
procedure TNode<T>.LoadFromStream( const AStream: TStream; const AReaderProc: TNodeReadProc; const AProgressProc: TNodeProgressProc = nil );
var
VIndex: Integer;
VCount: Integer;
VReader: TBinaryReader;
VCurrentNode: TNode<T>;
VEntryNode: TNode<T>;
begin
if Assigned( AReaderProc ) then
begin
Clear;
VReader := TBinaryReader.Create( AStream );
try
VCount := VReader.ReadInteger;
Data := AReaderProc( VReader );
SetCapacity( VReader.ReadInteger );
VCurrentNode := Self;
for VIndex := 0 to VCount - 1 do
begin
VEntryNode := VCurrentNode.Add( AReaderProc( VReader ) );
VEntryNode.SetCapacity( VReader.ReadInteger );
if VEntryNode.FCapacity > 0 then
VCurrentNode := VEntryNode
else if VCurrentNode.FCount = VCurrentNode.FCapacity then
if VCurrentNode.Parent <> nil then
begin
VCurrentNode := VCurrentNode.Parent;
while ( VCurrentNode.FCount = VCurrentNode.FCapacity ) and ( VCurrentNode <> Self ) do
if VCurrentNode.Parent = nil then
VCurrentNode := Self
else
VCurrentNode := VCurrentNode.Parent;
end else
VCurrentNode := Self;
if Assigned( AProgressProc ) then
AProgressProc( VIndex, VCount );
end;
finally
VReader.DisposeOf;
end;
end;
end;
procedure TNode<T>.SaveToFile( const AFileName: TFileName; const AWriterProc: TNodeBufferedWriteProc; const AProgressProc: TNodeProgressProc = nil );
var
VCount: Integer;
VIndex: Integer;
VWriter: TBufferedWriter;
begin
if Assigned( AWriterProc ) then
begin
VCount := Count( True );
VIndex := 0;
VWriter := TBufferedWriter.Create( AFileName );
try
VWriter.WriteInteger( VCount );
AWriterProc( VWriter, Data );
VWriter.WriteInteger( Count );
if Assigned( AProgressProc ) then
begin
AProgressProc( VIndex, VCount );
Inc( VIndex );
end;
ForEach(
procedure( const ANode: TNode<T>; var ABreak: Boolean )
begin
AWriterProc( VWriter, ANode.Data );
VWriter.WriteInteger( ANode.Count );
if Assigned( AProgressProc ) then
begin
AProgressProc( VIndex, VCount );
Inc( VIndex );
end;
end
);
finally
VWriter.DisposeOf;
end;
end;
end;
procedure TNode<T>.LoadFromFile( const AFileName: TFileName; const AReaderProc: TNodeBufferedReadProc; const AProgressProc: TNodeProgressProc = nil );
var
VIndex: Integer;
VCount: Integer;
VReader: TBufferedReader;
VCurrentNode: TNode<T>;
VEntryNode: TNode<T>;
begin
if Assigned( AReaderProc ) then
begin
Clear;
VReader := TBufferedReader.Create( AFileName );
try
VCount := VReader.ReadInteger;
Data := AReaderProc( VReader );
SetCapacity( VReader.ReadInteger );
VCurrentNode := Self;
for VIndex := 0 to VCount - 1 do
begin
VEntryNode := VCurrentNode.Add( AReaderProc( VReader ) );
VEntryNode.SetCapacity( VReader.ReadInteger );
if VEntryNode.FCapacity > 0 then
VCurrentNode := VEntryNode
else if VCurrentNode.FCount = VCurrentNode.FCapacity then
if VCurrentNode.Parent <> nil then
begin
VCurrentNode := VCurrentNode.Parent;
while ( VCurrentNode.FCount = VCurrentNode.FCapacity ) and ( VCurrentNode <> Self ) do
if VCurrentNode.Parent = nil then
VCurrentNode := Self
else
VCurrentNode := VCurrentNode.Parent;
end else
VCurrentNode := Self;
if Assigned( AProgressProc ) then
AProgressProc( VIndex, VCount );
end;
finally
VReader.DisposeOf;
end;
end;
end;
end.
答案 0 :(得分:0)
向树中添加“some”多线程的最简单方法是将TCriticalSection对象添加到其根目录。关键在于确保保存和编辑不会同时发生。因此,当保存线程正在执行时,您可以使用主线程中的其他对象(例如,GUI可以正常工作),您也可以访问此树,但是当您在保存未执行时尝试编辑此树时,主线程将等待所有保存程序完成。
我想指出,在保存线程执行时添加新节点是有潜在危险的,因为它需要更新节点列表。此时内存的重新分配可能会发生(在其他地方有更大的块来包含所有元素),此时保存线程访问列表将会大大失败!
有可能将关键部分放在树的每个节点上,这样我们的主线程只会在它修改此时正确保存的节点时才会等待,你可以使用TThreadList代替TArray来存储允许的节点列表你可以添加新的节点,而旧的节点仍在保存,但这样你就有了成千上万的可能性来拍摄你自己的腿,只有极其小心它才能正常工作
所以我认为,您应该从一个关键部分开始,看看它是否足够快以满足您的需求。
答案 1 :(得分:0)
最后,感谢您的建议,这是我能做的最好的事情。 不幸的是,速度慢,并且在保存步骤中使用了双倍内存。
手机上的时间被认为是x500,而节省步骤x1000:
使用1.000.000短字符串在Interl I7中测试:&#34;项目索引XXX.XXX&#34;
谢谢大家。
一些日志:
...
1719毫秒的ForEach(1000000)
在1250毫秒内删除(1000000)
在3078毫秒内保存文件(940150)
在1828毫秒内添加(1000000)
1094毫秒内的ForEach(1000000)
在1219毫秒内添加(1000000)
在1406毫秒内删除(970781)
ForEach(970781)in 1625 ms
在1703毫秒内添加(1000000)
在531毫秒内删除(1000000)
在3625毫秒内保存文件(1000000)
ForEach(1000000)in 813 ms
在1016毫秒内添加(1000000)
...
简单的课程:
TThreadSafeNode<T> = class
private
FNode: TNode<T>;
FCriticalSection: TCriticalSection;
public
constructor Create;
destructor Destroy; override;
function Lock: TNode<T>;
procedure Unlock;
end;
测试线程:
var
VThreadSafeNode: TThreadSafeNode<String>;
begin
Randomize;
VThreadSafeNode := TThreadSafeNode<String>.Create;
// ---------------------------------------------------------------------------
TThread.CreateAnonymousThread(
procedure
var
VIndex: Integer;
VCount: Integer;
VTickCount: Cardinal;
VRoot: TNode<String>;
begin
while True do
begin
VTickCount := TThread.GetTickCount;
VRoot := VThreadSafeNode.Lock;
try
VRoot.Clear;
VRoot.Capacity := Nodes;
for VIndex := 1 to Nodes do
begin
VRoot.Add( 'Item Index ' + VIndex.ToString );
end;
VCount := VRoot.Count( True );
finally
VThreadSafeNode.Unlock;
end;
TThread.Synchronize(
nil,
procedure
begin
Memo1.Lines.Add( 'Adding ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
end
);
end;
end
).Start;
TThread.CreateAnonymousThread(
procedure
var
VIndex: Integer;
VCount: Integer;
VTickCount: Cardinal;
VRoot: TNode<String>;
begin
while True do
begin
VTickCount := TThread.GetTickCount;
VRoot := VThreadSafeNode.Lock;
try
VIndex := Random( VRoot.Count );
VRoot.Delete( VIndex, Random( VRoot.Count ) - VIndex );
VCount := VRoot.Count( True );
finally
VThreadSafeNode.Unlock;
end;
TThread.Synchronize(
nil,
procedure
begin
Memo1.Lines.Add( 'Deleting ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
end
);
TThread.Sleep( Random( 1000 ) );
end;
end
).Start;
TThread.CreateAnonymousThread(
procedure
var
VCount: Integer;
VTickCount: Cardinal;
VRoot: TNode<String>;
begin
while True do
begin
VTickCount := TThread.GetTickCount;
VRoot := VThreadSafeNode.Lock;
try
VRoot.ForEach(
procedure( const Item: TNode<String>; var ABreak: Boolean )
begin
Item.Value := Item.Value;
end
);
VCount := VRoot.Count( True );
finally
VThreadSafeNode.Unlock;
end;
TThread.Synchronize(
nil,
procedure
begin
Memo1.Lines.Add( 'ForEach ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
end
);
TThread.Sleep( Random( 1000 ) );
end;
end
).Start;
TThread.CreateAnonymousThread(
procedure
var
VCount: Integer;
VLastPercent: Integer;
VTickCount: Cardinal;
VRoot: TNode<String>;
begin
while True do
begin
VTickCount := TThread.GetTickCount;
VLastPercent := 0;
VRoot := TNode<String>.Create;
try
VRoot.Assign( VThreadSafeNode.Lock );
finally
VThreadSafeNode.Unlock;
end;
try
VRoot.SaveToFile(
TPath.Combine( TPath.GetSharedDocumentsPath, 'test.txt' ),
procedure( const AWriter: TBufferedWriter; const AData: String )
begin
AWriter.WriteString( AData );
end,
procedure( const AIndex, ACount: Integer )
var
VPercent: Integer;
begin
VPercent := Trunc( ( ( AIndex + 1 ) * 100 ) / ACount );
if VLastPercent <> VPercent then
begin
VLastPercent := VPercent;
TThread.Synchronize(
nil,
procedure
begin
ProgressBar1.Value := VLastPercent;
end
);
end;
end
);
VCount := VRoot.Count( True );
finally
VRoot.DisposeOf;
end;
TThread.Synchronize(
nil,
procedure
begin
Memo1.Lines.Add( 'Saving File ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
end
);
TThread.Sleep( Random( 1000 ) );
end;
end
).Start;
end;