スレッドを閉じるときにデッドロック
-
23-08-2019 - |
質問
私は、COMポートをオープンしたクラスを作成し、ハンドルは読み取りおよび書き込み操作を重複していました。読み込み1とのデータの書き込みを行う1 - これは、2つの独立したスレッドが含まれています。それらの両方が終了した読み取りまたは書き込み操作に関する通知(例えばOnReadまたはOnWrite)XXXの手続きを呼び出します。
以下は、スレッドがどのように動作するかのアイデアの短い例があります:
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クラスの書き込み()プロシージャを呼び出すときに同じクリティカルセクションを入力しようと書き込まれる新しい値をキューに入れることができるという事実のため。
そして、ここでは、デッドロックを持っています。同時に、そのスレッドが解放されるクリティカルセクションを待機しながら、close()メソッドを呼び出したスレッドは、クリティカルセクションに入り、その後クローズする書き込みスレッドを待つ。
私はかなり長い間考えてきたと私はその問題への解決策を見つけるために管理していませんでした。事は私が閉じる()メソッドが残っている時に何の読み出し/書き込みスレッドが、私はちょうどそれらのスレッドの終端フラグを設定し、離れることができないことを意味し、生きていないことを確認してくださいになりたいということです。
はどのように問題を解決することができますか?たぶん私は、非同期シリアルポートを処理する私のアプローチを変更する必要がありますか?
事前にご相談いただきありがとうございます。
マリウシュます。
--------- EDIT ----------
どのような解決策は?
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;
私の考えが正しければ、これはデッドロックの問題を解消する必要があります。書き込みスレッドが閉じられる前に、しかし、残念ながら、私は、通信ポートのハンドルを閉じます。これは、引数の1つ(例えば、読み取り、WaitCommEventを書く)と通信ポートのハンドルを取る任意のメソッドを呼び出すときに例外がそのスレッドに提起しなければならないことを意味しています。私はそのスレッドでその例外をキャッチした場合、それはアプリケーション全体の作業に影響を与えないことを確認することができますか?この質問は愚かに聞こえるかもしれないが、私はいくつかの例外は、右、それを引き起こしたアプリケーションを閉じるようにOSを引き起こすかもしれないと思いますか?私はこのときのことを心配する必要がありますか?
解決
はい、あなたはおそらく、あなたのアプローチを再考する必要があります。非同期操作は、スレッドの必要性を排除するために、正確にご利用いただけます。あなたがスレッドを使用する場合は、同期(ブロッキング)呼び出しを使用します。あなたは非同期操作を使用する場合は、1つのスレッドですべてを処理 - 必ずしもメインスレッドが、それはIMO送信と異なるスレッドでの受信を行うには意味がありません。
。があり、あなたの同期の問題の周りのコースのいくつかの方法ですが、私はむしろデザインを変更したい。
他のヒント
あなたは閉じるのロックアウトを取ることができます。それはWAITFORから戻った時点で、スレッド本体は、それが終了しているに気づいた最後のループを完了し、終了しました。
あなたがこれをやって幸せに感じていない場合は、、そしてあなただけFreeAndNil前にロックを設定し移動することができます。これは、明示的にロックを適用する前に、スレッドのシャットダウンメカニズムが働くことができます(それはロックのために何と競合する必要はありませんので)。
EDITます:
(1)あなたも途切れが実行中のループの後、またはスレッドのデストラクタでそれを行う取り扱うクローズします。
(2)申し訳ありませんが、編集したソリューションはひどい混乱です。終了し、WAITFORは完全に安全に、あなたが必要なすべてを行います。
主な問題は、クリティカルセクションで閉じるの全体の内容を置くことのようです。私はほとんど確信している(ただし、ドキュメントをチェックする必要があります)TThread.TerminateとTThread.WaitForはセクション外部から呼び出すことが安全であること。クリティカルセクション外でその部分を引っ張って、あなたはデッドロックを解決します。