我有一个线程类TValidateInvoiceThread:
type
TValidateInvoiceThread = class(TThread)
private
FData: TValidationData;
FInvoice: TInvoice; // Do NOT free
FPreProcessing: Boolean;
procedure ValidateInvoice;
protected
procedure Execute; override;
public
constructor Create(const objData: TValidationData; const bPreProcessing: Boolean);
destructor Destroy; override;
end;
constructor TValidateInvoiceThread.Create(const objData: TValidationData;
const bPreProcessing: Boolean);
var
objValidatorCache: TValidationCache;
begin
inherited Create(False);
FData := objData;
objValidatorCache := FData.Caches.Items['TInvAccountValidator'];
end;
destructor TValidateInvoiceThread.Destroy;
begin
FreeAndNil(FData);
inherited;
end;
procedure TValidateInvoiceThread.Execute;
begin
inherited;
ValidateInvoice;
end;
procedure TValidateInvoiceThread.ValidateInvoice;
var
objValidatorCache: TValidationCache;
begin
objValidatorCache := FData.Caches.Items['TInvAccountValidator'];
end;
我在另一个类
中创建这个线程 procedure TInvValidators.ValidateInvoiceUsingThread(
const nThreadIndex: Integer;
const objValidatorCaches: TObjectDictionary<String, TValidationCache>;
const nInvoiceIndex: Integer; const bUseThread, bPreProcessing: Boolean);
begin
objValidationData := TValidationData.Create(FConnection, FAllInvoices, FAllInvoices[nInvoiceIndex], bUseThread);
objValidationData.Caches := objValidatorCaches;
objThread := TValidateInvoiceThread.Create(objValidationData, bPreProcessing);
FThreadArray[nThreadIndex] := objThread;
FHandleArray[nThreadIndex]:= FThreadArray[nThreadIndex].Handle;
end;
然后我执行它
rWait:= WaitForMultipleObjects(FThreadsRunning, @FHandleArray, True, 100);
注意我已经删除了一些代码,试图让它更容易跟随
问题是我的词典变得腐败
如果我在构造函数中放置一个断点,那么一切都很好
但是,在Execute方法的第一行中,字典现在已损坏。
字典本身是类
的全局变量我是否需要做一些特别的事情以允许我在线程中使用字典?
我也遇到了与字符串列表相同的问题
编辑 - 按要求提供其他信息
TInvValidators包含我的词典
TInvValidators = class(TSTCListBase)
private
FThreadArray : Array[1..nMaxThreads] of TValidateInvoiceThread;
FHandleArray : Array[1..nMaxThreads] of THandle;
FThreadsRunning: Integer; // total number of supposedly running threads
FValidationList: TObjectDictionary<String, TObject>;
end;
procedure TInvValidators.Validate(
const Phase: TValidationPhase;
const objInvoices: TInvoices;
const ReValidate: TRevalidateInvoices;
const IDs: TList<Integer>;
const objConnection: TSTCConnection;
const ValidatorCount: Integer);
var
InvoiceIndex: Integer;
i : Integer;
rWait : Cardinal;
Flags: DWORD; // dummy variable used in a call to find out if a thread handle is valid
nThreadIndex: Integer;
procedure ValidateInvoiceRange(const nStartInvoiceID, nEndInvoiceID: Integer);
var
InvoiceIndex: Integer;
I: Integer;
begin
nThreadIndex := 1;
for InvoiceIndex := nStartInvoiceID - 1 to nEndInvoiceID - 1 do
begin
if InvoiceIndex >= objInvoices.Count then
Break;
objInvoice := objInvoices[InvoiceIndex];
ValidateInvoiceUsingThread(nThreadIndex, FValidatorCaches, InvoiceIndex, bUseThread, False);
Inc(nThreadIndex);
if nThreadIndex > nMaxThreads then
Break;
end;
FThreadsRunning := nMaxThreads;
repeat
rWait:= WaitForMultipleObjects(FThreadsRunning, @FHandleArray, True, 100);
case rWait of
// one of the threads satisfied the wait, remove its handle
WAIT_OBJECT_0..WAIT_OBJECT_0 + nMaxThreads - 1: RemoveHandle(rWait + 1);
// at least one handle has become invalid outside the wait call,
// or more than one thread finished during the previous wait,
// find and remove them
WAIT_FAILED:
begin
if GetLastError = ERROR_INVALID_HANDLE then
begin
for i := FThreadsRunning downto 1 do
if not GetHandleInformation(FHandleArray[i], Flags) then // is handle valid?
RemoveHandle(i);
end
else
// the wait failed because of something other than an invalid handle
RaiseLastOSError;
end;
// all remaining threads continue running, process messages and loop.
// don't process messages if the wait returned WAIT_FAILED since we didn't wait at all
// likewise WAIT_OBJECT_... may return soon
WAIT_TIMEOUT: Application.ProcessMessages;
end;
until FThreadsRunning = 0; // no more valid thread handles, we're done
end;
begin
try
FValidatorCaches := TObjectDictionary<String, TValidationCache>.Create([doOwnsValues]);
for nValidatorIndex := 0 to Count - 1 do
begin
objValidator := Items[nValidatorIndex];
objCache := TValidationCache.Create(objInvoices);
FValidatorCaches.Add(objValidator.ClassName, objCache);
objValidator.PrepareCache(objCache, FConnection, objInvoices[0].UtilityType);
end;
nStart := 1;
nEnd := nMaxThreads;
while nStart <= objInvoices.Count do
begin
ValidateInvoiceRange(nStart, nEnd);
Inc(nStart, nMaxThreads);
Inc(nEnd, nMaxThreads);
end;
finally
FreeAndNil(FMeterDetailCache);
end;
end;
如果我删除重复,直到只留下WaitForMultipleObjects,我仍然会遇到很多错误
你可以在这里看到我正在处理不超过nMaxThreads(10)的块的发票
当我恢复重复直到循环时,它在我的虚拟机上运行,但随后在我的主机(具有更多可用内存)上违反了访问权限
保
答案 0 :(得分:3)
在我提供如何解决问题的指导之前,我会给你一个非常重要的提示。
在尝试使多线程实现正常工作之前,首先确保您的代码单线程。关键是多线程代码增加了一个全新的复杂层。在您的代码在单个线程中正常工作之前,它无法在多个线程中执行此操作。额外的复杂性使得修复非常困难。
您可能认为自己有一个可行的单线程解决方案,但我发现您的代码中存在错误,这意味着您仍然存在大量资源管理错误。这是一个只有相关行的例子,以及解释错误的评论:
begin
try //try/finally is used for resource protection, in order to protect a
//resource correctly, it should be allocated **before** the try.
FValidatorCaches := TObjectDictionary<String, TValidationCache>.Create([doOwnsValues]);
finally
//However, in the finally you're destroying something completely
//different. In fact, there are no other references to FMeterDetailCache
//anywhere else in the code you've shown. This strongly implies an
//error in your resource protection.
FreeAndNil(FMeterDetailCache);
end;
end;
你说的是:&#34;在Execute方法的第一行,字典现在已经损坏&#34;。
首先,我相当确定你的词典并没有真正“腐败”#34; “&#34;腐败&#34;暗示它在那里,但其内部数据无效导致行为不一致。当Execute
方法想要使用字典时,它很可能已被破坏。因此,您的线程基本上指向用于拥有字典的内存区域,但它根本不再存在。 (即没有&#34;腐败&#34;)
SIDE NOTE 由于您有多个线程共享同一个字典,因此您的字典可能会真正损坏。如果不同的线程同时导致字典的任何内部更改,它很容易变得腐败。但是,假设您的线程都将字典视为只读,则需要使用内存覆盖来破坏它。
因此,让我们关注可能导致您的字典在线程使用之前被销毁的内容。 注意我无法在提供的代码中看到任何内容,但有两种可能:
在第一种情况下,这将发生如下:
Main Thread: ......C......D........
Child Thread ---------S......
. = code being executed
C = child thread created
- = child thread exists, but isn't doing anything yet
S = OS has started the child thread
D = main thread destroys dictionary
上述观点是,即使在子线程开始运行之前,主线程也很容易忘记它决定销毁字典的那一点。
至于第二种可能性,这取决于TValidationData
的析构函数内发生的事情。由于您还没有显示该代码,只有您知道答案。
假设字典过早被破坏,稍微调试可以快速查明字典被破坏的位置/原因。从您的问题来看,您似乎已经完成了一些调试,所以我假设您可以顺利执行以下步骤:
Execute
,则该主题应该仍然可以使用该字典。Execute
之前到达字典的析构函数,那么您只需要检查导致对象销毁的调用顺序。对内存覆盖的可能性保持开放态度......调试起来有点棘手。但如果您可以始终如一地重现问题,则应该可以进行调试。
@FData.Caches
,找到字典的地址。您有许多问题和陈述意味着对线程之间共享数据(字典/字符串列表)的误解。我会尝试覆盖这里的那些。
FHandleArray
的某些条目无法初始化。你还好吗?最后,特别提一下:
使用多个线程执行字典查找效率极低。字典查找是O(1)操作。线程的开销几乎肯定会让你失望,除非你打算在字典查找之外做大量的处理。
我在你的代码中注意到以下内容,这是一个错误。
procedure TValidateInvoiceThread.Execute;
begin
inherited;
ValidateInvoice;
end;
TThread.Execute
方法抽象,这意味着没有实现。尝试调用抽象方法将触发EAbstractError
。幸运的是,正如LU RD指出的那样,编译器能够通过不编译行来保护您。即便如此,在此处不调用继承会更准确。
注意:一般来说,被覆盖的方法并不总是需要调用继承的。您应该明确知道继承正在为您做什么,并决定是否在逐个案例的基础上调用它。不要仅仅因为您要覆盖虚拟方法而进入自动驾驶模式调用继承。