سؤال

لدينا ملف 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); 

الذي ينتظر إلى الأبد.

لذا بينما يعمل الإصلاح الذي قمت به أعلاه، هناك شيء أعمق في هذا!

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top