الجمود عند إغلاق الموضوع
-
23-08-2019 - |
سؤال
قمت بإنشاء فئة تفتح منفذ COM ومقبض عمليات القراءة والكتابة متداخلة. أنه يحتوي على اثنين من المواضيع المستقلة - واحد يقرأ وواحد يكتب البيانات. كلاهما يسمي إجراءات Onxxx (مثل Onread أو Onwrite) إخطار حول عملية القراءة أو الكتابة المنتهية.
فيما يلي مثال قصير عن فكرة كيفية عمل الخيوط:
TOnWrite = procedure (Text: string);
TWritingThread = class(TThread)
strict private
FOnWrite: TOnWrite;
FWriteQueue: array of string;
FSerialPort: TAsyncSerialPort;
protected
procedure Execute; override;
public
procedure Enqueue(Text: string);
{...}
end;
TAsyncSerialPort = class
private
FCommPort: THandle;
FWritingThread: TWritingThread;
FLock: TCriticalSection;
{...}
public
procedure Open();
procedure Write(Text: string);
procedure Close();
{...}
end;
var
AsyncSerialPort: TAsyncSerialPort;
implementation
{$R *.dfm}
procedure OnWrite(Text: string);
begin
{...}
if {...} then
AsyncSerialPort.Write('something');
{...}
end;
{ TAsyncSerialPort }
procedure TAsyncSerialPort.Close;
begin
FLock.Enter;
try
FWritingThread.Terminate;
if FWritingThread.Suspended then
FWritingThread.Resume;
FWritingThread.WaitFor;
FreeAndNil(FWritingThread);
CloseHandle(FCommPort);
FCommPort := 0;
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Open;
begin
FLock.Enter;
try
{open comm port}
{create writing thread}
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Write(Text: string);
begin
FLock.Enter;
try
{add Text to the FWritingThread's queue}
FWritingThread.Enqueue(Text);
finally
FLock.Leave;
end;
end;
{ TWritingThread }
procedure TWritingThread.Execute;
begin
while not Terminated do
begin
{GetMessage() - wait for a message informing about a new value in the queue}
{pop a value from the queue}
{write the value}
{call OnWrite method}
end;
end;
عند إلقاء نظرة على إجراء إغلاق ()، سترى أنه يدخل القسم الحرج، إنهاء مؤشر ترابط الكتابة ثم ينتظره حتى ينتهي. بسبب حقيقة أن مؤشر ترابط الكتابة يمكن أن يؤدي إلى كتابة قيمة جديدة عند استدعاء طريقة Onwrite، سيحاول إدخال نفس القسم الحرج عند الاتصال بإجراء الكتابة () لفئة TasyncSerialPort.
وهنا لدينا حالة توقف تام. تم إدخال مؤشر الترابط الذي يسمى الأسلوب الإغلاق () القسم الحرج ثم ينتظر إغلاق مؤشر ترابط الكتابة، بينما في نفس الوقت الذي ينتظر الخيط من أجل إطلاق سراح القسم الحرج الذي سيتم تحريره.
لقد كنت أفكر لفترة طويلة ولم أتمكن من العثور على حل لهذه المشكلة. الشيء هو أنني أود أن أكون متأكدا من عدم وجود مؤشر ترابط القراءة / الكتابة على قيد الحياة عندما يتم ترك الطريقة الإغلاق ()، مما يعني أنني لا أستطيع ضبط العلم المستغرق من هذه المواضيع والمغادرة.
كيف يمكنني حل المشكلة؟ ربما يجب علي تغيير نهجي لمعالجة المنفذ التسلسلي غير متزامن؟
شكرا لنصيحتك مقدما.
ماريوس.
--------- تعديل ----------
ماذا عن هذا الحل؟
procedure TAsyncSerialPort.Close;
var
lThread: TThread;
begin
FLock.Enter;
try
lThread := FWritingThread;
if Assigned(lThread) then
begin
lThread.Terminate;
if lThread.Suspended then
lThread.Resume;
FWritingThread := nil;
end;
if FCommPort <> 0 then
begin
CloseHandle(FCommPort);
FCommPort := 0;
end;
finally
FLock.Leave;
end;
if Assigned(lThread) then
begin
lThread.WaitFor;
lThread.Free;
end;
end;
إذا كان تفكيري صحيحا، فيجب أن يخلص هذا إلى القضاء على مشكلة الجمود. لسوء الحظ، ومع ذلك، أغلق مقبض منفذ Comm قبل إغلاق مؤشر ترابط الكتابة. هذا يعني أنه عندما يستدعي أي طريقة تأخذ مقبض منفذ Comm كأحد حججه (مثل الكتابة والقراءة وينتظره) يجب رفع استثناء في هذا الموضوع. هل يمكن أن أكون متأكدا من أنه إذا قبضت على هذا الاستثناء في هذا الموضوع، فلن يؤثر ذلك على عمل التطبيق بأكمله؟ قد يبدو هذا السؤال غبي، لكنني أعتقد أن بعض الاستثناءات قد تتسبب في إغلاق نظام التشغيل التطبيق الذي تسبب في ذلك، أليس كذلك؟ هل يجب أن تقلق بشأن ذلك في هذه الحالة؟
المحلول
نعم، يجب عليك إعادة النظر في نهجك. تتوفر العمليات غير المتزامنة تماما للقضاء على الحاجة إلى المواضيع. إذا كنت تستخدم الخيوط، فاستخدم مكالمات (حظر) متزامن. إذا كنت تستخدم العمليات غير المتزامنة، فالتعامل مع كل شيء في مؤشر ترابط واحد - ليس بالضرورة الترابط الرئيسي، لكنه لا معنى له المنظمة البحرية الدولية للقيام بالإرسال والاستقبال في مؤشرات ترابط مختلفة.
هناك طرق بالطبع حول مشكلة المزامنة الخاصة بك، لكنني أفضل تغيير التصميم.
نصائح أخرى
يمكنك أن تأخذ القفل من الإغلاق. بحلول الوقت الذي تقوم فيه بإرجاع من Waitfor، لاحظت هيئة الخيط أنه تم إنهاءها، أكملت الحلقة الأخيرة، وانتهت.
إذا كنت لا تشعر بالسعادة في القيام بذلك، فيمكنك نقل تحديد القفل قبل Freeandnil. يتيح هذا صراحة أن آليات إيقاف تشغيل مؤشر الترابط تعمل قبل تطبيق القفل (لذلك لن تضطر إلى التنافس مع أي شيء من أجل القفل)
تعديل:
(1) إذا كنت ترغب أيضا في إغلاق مقبض CUMS القيام بذلك بعد الحلقة في تنفيذ، أو في Destructor المؤشرات.
(2) آسف، ولكن حل التحرير الخاص بك هو فوضى فظيعة. إنهاء وينتظر سيفعل كل ما تحتاجه، بأمان تماما.
يبدو أن المشكلة الرئيسية أنها تضع محتوى كامل من قريب في قسم حرج. أنا متأكد تقريبا (ولكن عليك التحقق من المستندات) أن TTHREAD.TERMINES و TTHREAD.WAITFOR من آمنة للاتصال من خارج القسم. من خلال سحب هذا الجزء خارج القسم الحرج، سوف تحل محل الجمود.