Question

J'ai créé une classe qui ouvre un port COM et les poignées se chevauchaient lecture et d'écriture. Il contient deux threads indépendants - un qui lit et qui écrit des données. Les deux d'entre eux appellent des procédures OnXXX (par exemple OnRead ou OnWrite) informer sur le fonctionnement lecture ou d'écriture terminée.

Ce qui suit est un court exemple de l'idée comment les discussions de travail:

  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;

Quand vous regardez la procédure Close (), vous verrez qu'il pénètre dans la section critique, arrête le thread d'écriture et attend qu'elle se termine. En raison du fait que le fil d'écriture peut enqueue une nouvelle valeur à écrire quand il appelle la méthode OnWrite, il va essayer d'entrer dans la même section critique lors de l'appel de la procédure Write () de la classe TAsyncSerialPort.

Et nous avons une impasse. Le fil qui a appelé la méthode Close () est entré dans la section critique, puis attend que le fil d'écriture pour être fermé, tandis que dans le même temps que thread attend la section critique à être libérés.

J'ai pensé pendant un temps assez long et je ne l'ai pas réussi à trouver une solution à ce problème. La chose est que je voudrais être sûr que pas de lecture / d'écriture fil est en vie lorsque la méthode Close () est à gauche, ce qui signifie que je ne peux pas mettre le drapeau Terminated de ces fils et laisser.

Comment puis-je résoudre le problème? Peut-être que je devrais changer mon approche de manipulation port série de manière asynchrone?

Merci pour vos conseils à l'avance.

Mariusz.

--------- ---------- EDIT
Que diriez-vous d'une telle solution?

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;

Si ma pensée est correcte, cela devrait éliminer le problème de blocage. Malheureusement, cependant, je ferme gérer le port de communication avant que le thread d'écriture est fermé. Cela signifie que lorsqu'il appelle une méthode qui prend le port comm poignée comme l'un de ses arguments (par exemple, écrire, lire, WaitCommEvent) devrait être soulevé une exception dans ce thread. Puis-je être sûr que si je prends cette exception dans ce fil, il ne sera pas affecter le travail de l'ensemble de l'application? Cette question peut paraître stupide, mais je pense que certaines exceptions peuvent provoquer le système d'exploitation pour fermer l'application qui a causé, non? Dois-je à vous soucier que dans ce cas?

Était-ce utile?

La solution

Oui, vous devriez probablement revoir votre approche. Les opérations asynchrones sont disponibles exactement pour éliminer la nécessité pour les fils. Si vous utilisez les threads, utilisez alors des appels synchrones (blocage). Si vous utilisez des opérations asynchrones, puis tout gérer dans un thread -. Pas nécessairement le fil conducteur, mais il n'a pas de sens de l'OMI à faire l'envoi et la réception dans différents threads

Il y a des façons de cours autour de votre problème de synchronisation, mais je préfère changer la conception.

Autres conseils

Vous pouvez prendre le lock-out à la fermeture. Au moment où il revient de la WaitFor, le corps de fil a remarqué qu'il a été mis fin, a terminé la dernière boucle, et terminée.

Si vous ne vous sentez pas heureux de faire cela, alors vous pouvez passer d'un réglage de la serrure juste avant la FreeAndNil. Cela permet explicitement les mécanismes d'arrêt de fil fonctionnent avant d'appliquer le verrou (donc il ne sera pas en concurrence avec quoi que ce soit pour la serrure)

EDIT:

(1) Si vous voulez aussi fermer les comms gérer le faire après la boucle dans l'exécution, ou dans le destructor du fil.

(2) Désolé, mais votre solution éditée est un terrible gâchis. Et Terminate WaitFor fera tout ce dont vous avez besoin, parfaitement en toute sécurité.

Le principal problème semble être que vous placez tout le contenu de proximité dans une section critique. Je suis presque sûr (mais vous devrez vérifier la documentation) qui TThread.Terminate et TThread.WaitFor sont sûrs de faire appel à l'extérieur de la section. En tirant cette partie en dehors de la section critique vous permettra de résoudre l'impasse.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top