AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Zwei Threads wollen etwas in die Queue stopfen

Ein Thema von TiGü · begonnen am 25. Sep 2015 · letzter Beitrag vom 28. Sep 2015
Antwort Antwort
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.070 Beiträge
 
Delphi 10.4 Sydney
 
#1

Zwei Threads wollen etwas in die Queue stopfen

  Alt 25. Sep 2015, 15:35
Hallo miteinander,

ich habe wieder ein merkwürdiges Problem und suche jemanden, der mir den Knoten im Kopf löst.
Ich habe mein Problem auf ein kleines Testprojekt reduzieren können (siehe Anhang). Es besteht aus einen Formular mit einen Start-Button und einen Memo.
Es ist mit DX erstellt, sollte sich aber so auch in jeder Version größer XE2 öffnen lassen?!

Problembeschreibung:
In einer DLL laufen zwei Threads, die Daten produzieren und per Callback an das Hauptprogramm übertragen.
Da die Callback im Kontext des Threads aufgerufen wird, verbietet sich hier natürlich der direkte Zugriff auf Member des VCL-Mainthreads.
Daher stopfe ich die übergebenden Daten per TThread.Queue in die Warteschlange.

Delphi-Quellcode:
procedure TDataConsumerFrm.Notify(const AData: Pointer; const ADataCount: Cardinal);
var
  LArray: TArray<Integer>;
begin
  FLock.Enter;
  try
    if Assigned(AData) and (ADataCount > 0) then
    begin
      LArray := Copy(TArray<Integer>(AData), 0, ADataCount);

      TThread.Queue(nil,
        procedure
        begin
          ShowData(LArray);
        end);
    end;
  finally
    FLock.Leave;
  end;
end;
Nach kurzer Laufzeit erhalte ich dann merkwürdige Zugriffsverletztungen, die ich mir so nicht erklären kann, da es ja eine zeitlang gut geht.
Code:
Project QueueTestProject.exe raised exception class $C0000005 with message 'access violation at 0x00000000: read of address 0x00000000'.
Project QueueTestProject.exe raised exception class EInvalidPointer with message 'Invalid pointer operation'.
Callstacks:
Code:
System._IntfCopy(???,???)
:0040cfbd @IntfCopy + $9
QueueFrm.TDataConsumerFrm.Notify($2D828E8,4)
QueueDLL.{Data.Producer.Impl}TDataProducer<System.Integer>.SendNotify
...

System.TObject.Create
QueueDLL.{Data.Producer.Impl}TDataProducer<System.Integer>.SendNotify
QueueDLL.{Data.Producer.Impl}TDataProducer<System.Integer>.Produce
...

System.SysGetMem(???)
:00405571 SysGetMem + $3D
:0116d3b6 {Data.Producer.Impl}TDataProducer<System.Integer>.SendNotify + $86
...

:7502c42d KERNELBASE.RaiseException + 0x58
System._Dispose(???,???)
System.ErrorAt(92,$40B05C)
System.Error(reInvalidPtr)
System._Dispose(???,???)
:0040b05c @Dispose + $C
Vcl.Forms.TApplication.WndProc((0, 0, 0, 0, 0, 0, (), 0, 0, (), 0, 0, ()))
System.Classes.StdWndProc(14223452,0,0,0)
:758862fa ; C:\Windows\syswow64\USER32.dll
...
Irgendwie klappt irgendwann das umkopieren der empfangenden Daten in das lokale LArray nicht mehr. Fragmentiere ich den Speicher zu sehr?
Über jeden Denkanstoß wäre ich dankbar.
Angehängte Dateien
Dateityp: zip QueueTest_2015-09-25.zip (14,2 KB, 8x aufgerufen)
  Mit Zitat antworten Zitat
Bambini
(Gast)

n/a Beiträge
 
#2

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 25. Sep 2015, 16:23
Irgendwie klappt irgendwann das umkopieren der empfangenden Daten in das lokale LArray nicht mehr. Fragmentiere ich den Speicher zu sehr?
Über jeden Denkanstoß wäre ich dankbar.
Der Thread.Queue() erlaubt, das die aufrufender Notify methode weiter augeführt wird, und wartet nicht auf dessen Ausführung. D.h. dein FLock.Leave und auch das Dispose deiner lokalen var LArray kann bereits erfolgt sein, bevor der Queue Aufruf stattfand.
Versuche es bitte mal mit TThread.Synchronize(nil, ...). Der warte solange, bis es durchgeführt wurde.

Geändert von Bambini (25. Sep 2015 um 16:40 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 25. Sep 2015, 16:33
Ein Dispose erfolgt nicht, denn die Variable ist im Scope der Anonymen Methode.

Den Lock kann man sich hier komplett sparen ... was soll denn hier überhaupt gelockt werden?
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.070 Beiträge
 
Delphi 10.4 Sydney
 
#4

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 25. Sep 2015, 16:42
Hm, das sieht mit Synchronize schon besser aus...aber auch hier treten sporadisch die merkwürdigen Effekte auf.
Mal kann so ein Durchlauf minutenlang ohen Fehler durchlaufen und beim nächsten Programstart braucht es nur wenige hundert Daten, um wieder Zugriffsverletzungen zu erzeugen...hm, ich werde noch irre.
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.070 Beiträge
 
Delphi 10.4 Sydney
 
#5

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 25. Sep 2015, 16:43
Den Lock kann man sich hier komplett sparen ... was soll denn hier überhaupt gelockt werden?
Die Notify-Methode im Formular wird von zwei verschiedenen Threads aus der DLL aufgerufen.
Ist das dann nicht notwendig?
  Mit Zitat antworten Zitat
Bambini
(Gast)

n/a Beiträge
 
#6

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 25. Sep 2015, 16:54
Ein Dispose erfolgt nicht, denn die Variable ist im Scope der Anonymen Methode.
Sicher? Das ist eine lokale var der Notify Methode. Wenn die beendet ist, werden diese aufgeräumt.
Ist die anonyme Methode bis dahin noch nicht fertig, kann sie auf diese nicht mehr zugreifen.
  Mit Zitat antworten Zitat
SMO

Registriert seit: 20. Jul 2005
178 Beiträge
 
Delphi XE6 Professional
 
#7

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 25. Sep 2015, 17:41
Sicher? Das ist eine lokale var der Notify Methode. Wenn die beendet ist, werden diese aufgeräumt.
Ist die anonyme Methode bis dahin noch nicht fertig, kann sie auf diese nicht mehr zugreifen.
Das stimmt nicht, Variablenbindung ist eines der Hauptmerkmale von anonymen Methoden.
  Mit Zitat antworten Zitat
einbeliebigername

Registriert seit: 24. Aug 2004
140 Beiträge
 
Delphi XE8 Professional
 
#8

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 27. Sep 2015, 00:00
Hallo,

mir ist da was aufgefallen. Es muss nicht das Multithreaded sein. Es könnte auch das Hin- und Her-reichen von dynamischen Arrays in/aus einer DLL für das Problem verantwortlich sein. Du verwendest in deinem Beispiel weder ShareMem noch Vergleichbares.

Setze mal bitte zum testen ShereMem in beiden Projekten an oberster Stelle. Und dann lass den Cast TArray<Integer>(AData) weck und änder die Signatur, so das beide Seiten wissen, dass dort ein Dynamisches Array übergeben wird.

einbeliebigername.
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.070 Beiträge
 
Delphi 10.4 Sydney
 
#9

AW: Zwei Threads wollen etwas in die Queue stopfen

  Alt 28. Sep 2015, 10:13
Setze mal bitte zum testen ShereMem in beiden Projekten an oberster Stelle. Und dann lass den Cast TArray<Integer>(AData) weck und änder die Signatur, so das beide Seiten wissen, dass dort ein Dynamisches Array übergeben wird.
Das hat auf jeden Fall eine Verbesserung gebracht bzw. ich konnte mit dieser Methode kein Problem in den sechs Probeläufen feststellen.
Aber da die Lösung dann speziell auf mit Delphi erzeugte Programme zugeschnitten ist, bin ich damit nicht sehr zufrieden.
Ich will mir nicht die Möglichkeit verbauen mit einen C++ oder .NET-Clienten ggf. die Daten aus der DLL abzuholen.

Ich "queue" nun selbst, indem ich im Formular die Daten temporär zwischenhalte und im Application.OnIdle abhole.
Prinzipell das gleiche wie mit TThread.Queue oder .Synchronize, nur mit weniger Overhead.
Wenn jemand eine bessere Idee hat, nur raus damit.

Delphi-Quellcode:
unit QueueFrm;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes, System.SyncObjs, System.Generics.Collections,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  Data.Consumer.Intf;

type
  TDataConsumerFrm = class(TForm, IDataConsumer)
    btnStart: TButton;
    mmoLog: TMemo;
    procedure btnStartClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FQueue: TQueue<Integer>;
    procedure ShowData;
    procedure LogToMemo(const ADataStr: string);
  public
    procedure GetDataOnIdle(Sender: TObject; var Done: Boolean);
    procedure Notify(const AData: Pointer; const ADataCount: Cardinal); stdcall;
  end;

var
  DataConsumerFrm: TDataConsumerFrm;

procedure SubscribeConsumer(const ADataConsumer: IDataConsumer); stdcall; external 'QueueDLL.dll';

implementation

{$R *.dfm}

{ TDataConsumerFrm }

procedure TDataConsumerFrm.btnStartClick(Sender: TObject);
begin
  SubscribeConsumer(Self);
end;

procedure TDataConsumerFrm.FormCreate(Sender: TObject);
begin
  FQueue := TQueue<Integer>.Create;
  TThread.NameThreadForDebugging('VCL-MainThread', MainThreadID);
  Application.OnIdle := GetDataOnIdle;
end;

procedure TDataConsumerFrm.FormDestroy(Sender: TObject);
begin
  SubscribeConsumer(nil);
  FQueue.Free;
end;

procedure TDataConsumerFrm.GetDataOnIdle(Sender: TObject; var Done: Boolean);
var
  LValue: Integer;
begin
  ShowData;
end;

procedure TDataConsumerFrm.Notify(const AData: Pointer; const ADataCount: Cardinal);
var
  LDataPtr: PInteger;
  LValue: Integer;
  I: Integer;
begin
  System.TMonitor.Enter(FQueue);
  try
    if Assigned(AData) and (ADataCount > 0) then
    begin
      LDataPtr := AData;
      for I := 0 to ADataCount - 1 do
      begin
        LValue := PInteger(LDataPtr)^;
        FQueue.Enqueue(LValue);
        Inc(LDataPtr);
      end;
    end;
  finally
    System.TMonitor.Exit(FQueue);
  end;
end;

procedure TDataConsumerFrm.ShowData;
var
  LValue: Integer;
  LDataStr: string;
begin
  System.TMonitor.Enter(FQueue);
  try
    while FQueue.Count > 0 do
    begin
      LValue := FQueue.Dequeue;
      LDataStr := LValue.ToString;
      LogToMemo(LDataStr);
    end;
  finally
    System.TMonitor.Exit(FQueue);
  end;
end;

procedure TDataConsumerFrm.LogToMemo(const ADataStr: string);
begin
  mmoLog.Lines.Add(ADataStr);

  if mmoLog.Lines.Count > 5000 then
  begin
    mmoLog.Lines.Clear;
  end;
end;

end.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:52 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz