هل يعمل TThread بشكل مختلف في تطبيق وحدة تحكم Delphi 2006؟
-
07-07-2019 - |
سؤال
لدينا ملف COM dll ناضج جدًا، والذي نقوم باختباره باستخدام DUnit.يقوم أحد اختباراتنا الأخيرة بإنشاء عدد قليل من المواضيع، ويختبر الكائن من تلك المواضيع.يعمل هذا الاختبار بشكل جيد عند تشغيل الاختبار باستخدام الواجهة الأمامية لواجهة المستخدم الرسومية، ولكنه يتوقف عند تشغيله كتطبيق وحدة تحكم.إليك نظرة زائفة سريعة لما لدينا في الاختبار
SetupTest;
fThreadRefCount := 0; //number of active threads
Thread1 := TMyThread.Create(True);
Inc(fThreadRefCount);
Thread1.OnTerminate := HandleTerminate; //HandleOnTerminate decrements fThreadRefCount
Thread3 := TMyThread.Create(True);
Inc(fThreadRefCount);
Thread2.OnTerminate := HandleTerminate; //HandleOnTerminate decrements fThreadRefCount
Thread3 := TMyThread.Create(True);
Inc(fThreadRefCount);
Thread3.OnTerminate := HandleTerminate; //HandleOnTerminate decrements fThreadRefCount
Thread1.Resume;
Thread2.Resume;
Thread3.Resume;
while fThreadRefCount > 0 do
Application.ProcessMessages;
لقد حاولت عدم القيام بأي شيء في OnExecute، لذلك أنا متأكد من أنه ليس الكود الفعلي الذي أختبره.في وحدة التحكم، لا يتناقص fThreadRefCount أبدًا، بينما إذا قمت بتشغيله كتطبيق واجهة مستخدم رسومية، فلا بأس!
بقدر ما أستطيع أن أرى، لم يتم استدعاء الحدث OnTerminate.
المحلول
وتحتاج إلى توفير المزيد من البيانات.
لاحظ أن يسمى OnTerminate عبر مزامنة، الأمر الذي يتطلب الدعوة إلى CheckSynchronize في مرحلة ما في مكان ما. Application.ProcessMessages عادة يفعل ذلك، ولكن اعتمادا على كيفية تمت تهيئة VCL، فمن الممكن أن آلية تزامن لم مدمن مخدرات تماما معا في تطبيق وحدة التحكم.
في أي حال، يعمل هذا البرنامج كما هو متوقع على الجهاز الخاص بي:
uses Windows, SysUtils, Classes, Forms;
var
threadCount: Integer;
type
TMyThread = class(TThread)
public
procedure Execute; override;
class procedure Go;
class procedure HandleOnTerminate(Sender: TObject);
end;
procedure TMyThread.Execute;
begin
end;
class procedure TMyThread.Go;
function MakeThread: TThread;
begin
Result := TMyThread.Create(True);
Inc(threadCount);
Result.OnTerminate := HandleOnTerminate;
end;
var
t1, t2, t3: TThread;
begin
t1 := MakeThread;
t2 := MakeThread;
t3 := MakeThread;
t1.Resume;
t2.Resume;
t3.Resume;
while threadCount > 0 do
Application.ProcessMessages;
end;
class procedure TMyThread.HandleOnTerminate(Sender: TObject);
begin
InterlockedDecrement(threadCount);
end;
begin
try
TMyThread.Go;
except
on e: Exception do
Writeln(e.Message);
end;
end.
نصائح أخرى
كما أشار Barry بحق، ما لم يتم استدعاء CheckSyncronize، فلن يتم استدعاء المزامنة، وإذا لم يتم استدعاء المزامنة، فلن يتم إطلاق حدث OnTerminate.ما يبدو أنه يحدث هو أنه عندما أقوم بإجراء اختبارات الوحدة الخاصة بي كتطبيق وحدة تحكم، لا توجد رسائل في قائمة انتظار الرسائل، وبالتالي فإن Application.ProcessMessage، الذي يتم استدعاؤه من Processmessages، لا يمكنه الاتصال بـ checkSynchronize أبدًا.لقد قمت الآن بحل المشكلة عن طريق تغيير الحلقة إلى
While fThreadRefCount > 0 do
begin
Application.ProcessMessages;
CheckSynchronize;
end;
وهو يعمل الآن في وضعي وحدة التحكم وواجهة المستخدم الرسومية.
يبدو أن خطاف Wakeupmainthread بأكمله قد تم إعداده بشكل صحيح.إنه هذا الخطاف الذي ينشر رسالة WM_NULL التي تقوم بتشغيل عملية التحقق من المزامنة.لا يصل الأمر إلى هذا الحد في تطبيق وحدة التحكم.
مزيد من التحقيق
لذا تزامن يفعل يتم الاتصال بك.يستدعي DoTerminate Synchronize(CallOnTerminate) ولكن يوجد سطر هناك:
WaitForSingleObject(SyncProcPtr.Signal, Infinite);
الذي ينتظر إلى الأبد.
لذا بينما يعمل الإصلاح الذي قمت به أعلاه، هناك شيء أعمق في هذا!