24. juli 2005 - 00:48Der er
26 kommentarer og 3 løsninger
Program hænger
Når jeg eksekverer en funktion, så hænger programmet - og kommer ingen steder.
Er det ikke muligt at få Delphi til at vise præcist hvilket linje den går ned på? Mener det er muligt, men har ikke lige kunnet finde ud af hvor/hvordan..
Jeg har lavet 2 programmer som kommunikerer med hinanden vha. TCP. Problemet opstår når jeg sender en bestemt kommando fra klienten til server.
Den ekskverer et "klik" på en knap - og når det sker, hænger server-modulet. Og eftersom jeg ingen meddelelser får om hvorfor den hænger, er jeg lidt på bar bund.
Skal lige understreges at laver jeg det knap-klik manuelt, så virker det som det skal. Men gør jeg det vha. min remote-software, så hænger server-delen et eller andet sted under knap-klik proceduren..
I stedet for at bruge ShowMessages så kan du måske drage nytte af OutputDebugString, der indsætter en linie i Delphi's event log (ctrl-alt-v). Under Views, Debug windows er der mange muligheder for at se hvor det går galt. Skal det blive rigtigt hardcore, så kan du få den til at vise hvor i den oversatte kode at den dør - hvis altså den dør med fejl og ikke et uendeligt loop eller lignende.
Har du mulighed for at offentliggøre lidt kode? Mest interessant er nok der hvor du forbereder data og sender det - tilsvarende der hvor du læser data igen.
Det viser sig at det er følgende kode der får programmet til at hænge (ihvertfald hænger programmet ikke, hvis jeg fjerner den).
for i:=0 to (Length(Tracks)-1) do begin Index:=TrackListBox.Items.IndexOf(Tracks[i]); Index2:=TrackListBox2.Items.IndexOf(Tracks[i]); if (Index>=0) then TrackListBox.Items.Delete(Index); if (Index2>=0) then TrackListBox2.Items.Delete(Index2); TrackListBox2.Items.Add(Tracks[i]); end;
Det koden gør er at den skulle søge efter om nogle af de linjer jeg sætter ind i TrackListBox2, eksisterer i TrackListBox1. Gør de det, skal den slette dem fra TrackListBox1 (kan det gøres på en smartere måde)?
Hvad jeg ikke forstår er, at det fungerer perfekt hvis jeg laver det manuelle klik. Gør jeg det gennem remotecontrol'en, så hænger programmet.
Fjerner jeg koden der, så virker det manuelle klik og remotecontrol'en perfekt.
Lavede lige lidt om i koden. Den hænger dog stadig (hvis jeg ikke udelader den):
TrackListBox2.Clear; for i:=0 to (Length(Tracks)-1) do begin Index:=TrackListBox.Items.IndexOf(Tracks[i]); if (Index>=0) then TrackListBox.Items.Delete(Index); TrackListBox2.Items.Add(Tracks[i]); end;
Jeg tror det har noget at gøre med din length(Tracks) at gøre. Kan ikke se hvad det er for en tingest, men antager at det er en TStrings og så er din måde at lave det på forkert.
Jeg har lavet nedenstående procedure som måske kan hjælpe dig videre (<TfrmMain> skal du erstatte med din egen form):
procedure <TfrmMain>.InsertTracks(aTracks : TStrings); var i, Index : integer; begin TrackListBox1.Items.BeginUpdate; TrackListBox2.Items.BeginUpdate; try for i := 0 to aTracks.Count - 1 do begin Index := TrackListBox1.Items.IndexOf(aTracks[i]); if Index >= 0 then TrackListBox1.Items.Delete(Index); end; TrackListBox2.Items.Assign(aTracks); finally TrackListBox1.Items.EndUpdate; TrackListBox2.Items.EndUpdate; end; end;
Som jeg kalder således:
procedure <TfrmMain>.Button2Click(Sender: TObject); var Tracks : TStringList; begin Tracks := TStringList.Create; try Tracks.Add('A'); ... Tracks.Add('Z');
Hvis altså at Tracks er en TStrings tingest, så skal man ikke bruge length når den har en property der fortæller oplysningen. Length bruges kun til at returnere længden på en streng eller antallet af felter i et array (Borland undlod, af en eller anden grund, at lave String til et rigtigt objekt fra starten).
Vil skyde på, at det er tilfældigt at den returnerer et brugbart resultat.
Under alle omstændigheder, givet at tracks er en TStrings af en art (TStringList), så virker min procedure - den er af mig testet.
Værdierne læses ind fra en fil og array'et kan så se således ud: Tracks[0]:='Bane0': Tracks[1]:='Bane1': Tracks[2]:='Bane2': Tracks[3]:='Bane3':
Du skriver at length() netop kan bruges til at tælle antallet af felter i et array, hvilket jeg netop gerne vil. Men du mener stadig at min kode ikke dur - og jeg skal bruge din metode?
Jeg ville bruge min procedure og indlæse filen via en TStringList. Den har nemlig en LoadFromFile (og en LoadFromStream) og så har du et array hvor både strenglængden og antallet af linier er dynamisk op til 2Gb grænsen.
Arrays er noget som jeg anser som pre-Delphi. TStringListen er smart og hurtig.
procedure TForm1.TCPServerExecute(AContext: TIdContext); var Command: string; Command2: string; Index: Integer; begin Command:=Acontext.Connection.IOHandler.ReadLn; Command2:=Acontext.Connection.IOHandler.ReadLn;
// Load Profile if (Command='LP') then begin Index:=LogListBox.Items.IndexOf(AContext.Connection.Socket.Binding.PeerIP); If (Index>=0) then begin if (StrToInt(Command2)>=0) then begin LoadComboBox.ItemIndex:=StrToInt(Command2); Acontext.Connection.IOHandler.WriteLn('2'); LoadButton.Click; end;
if (StrToInt(Command2)<0) then begin Acontext.Connection.IOHandler.WriteLn('1'); end; end; end;
Hvis serveren modtager en en "LP"-kommando, så skal serveren gøre det ovenstående. Det foregår med IdTCP-componenten.
Mener du det er her den går galt (findes der en bedre måde at sende den kommando på?)?
TCPServer køren i en thread og der er sikkert det, der giver dig problemer. Du 'må' ikke kalde kode i din main thread, der 'skriver' på skærmen. Tag et kig i hjælpen under Synchronize.
At en TCP server køre i en tråd er ikke en problem i sig selv. Problemet er vis serveren køre i main tråden, at så vil det se ud som om at programmet hænger, fordi så vil den bare ligge og lytte konstant uden at opdatere ydre ting som fx gui. Nu har jeg ikke lige læst hele tråden, men jeg ved af erfaring. At når en server skal lytte skal man oprette en tråd til den som den selv kan ligge og køre i. Når "serveren" selv får tildelt en tråd, kan man stadig interegere med den, da man ikke benytter main tråden til lytning. Fx kan man lave en tofase terminering, så man kan bestemme hvornår server tråden skal dø.
while(isRunning) { //vi lytter, lytter og lytter } }
her kan du godt se, at det går galt, da når vi først kommer ind i lykken, bruger vi kun energi på at lytte. Dvs. hvis vi har noget gui fx. Så vil det ikke blive opdateret.
gør istedet på denne led, ved at lade en tråd tage sig af lytte løkken. I c#
public class Server { private Thread server;
public Server() { server = new Thread(ThreadStart(startNyTråd)); //startNyTråd er bare navnet på den metode der skal kaldes når den nye tråd startes. (kaldes et delegate) server.Start();//vi starter den nye tråd. Dvs vi returnere omgående samtidig med at den nye tråd går igang.
for(int tal=0; tal < 30000; tal++) Console.WriteLine(tal) }
public void startNyTråd() { //her kunne server lytningen finde sted. }
public static void Main() { new Server(); } }
Se her sker det. Vi laver et server objekt i main. I konstruktoren skabes der en ny tråd. Denne tråd startes med start metoden, der omgående returnere. Dvs. Her ville serveren lytte imens at den talte op fra 0 til 30000. Istedet for at tælle, kan dit program så fx lytte på gui input eller hvad du nu vil. Håber det er svar nok :-)
Java eksemplet viser faktisk, hvad der sker inde i TCPServerSocket. Den opretter en tråd til at modtage client requests og nye tråde til at behandle client requests.
Din OnExecute er altså en anden tråd en den din mainform køre i. Hvis du fra din OnExecute kalder funktioner i din main tråd kan det give virkelige underlige resultater. Især hvis det er noget GUI noget.
Synchronize er løsningen på dit problem. Der er glimrende eksempler i hjælpen.
Jeg er altså stadig ikke (selv efter at have kigget i hjælpen) i stand til at greje hvordan jeg bruger det synchonize-halløj. Findes der ikke noget mere detaljeret hjælp?
Forstår ikke hvorfor du snakker om syncronized. Syncronized er noget du normalt bruger til at beskytte en kritisk region. Altså en fælles resource som to eller flere tråde benytter sig af. Tror bare du skal finde ud af hvordan du får lavet en tråd. Det burde løse dit program hænger problem.
Måske kan dette hjælpe dig: Starting the thread
When creating a thread without the TThread class, always use the BeginThread function from the SysUtils unit. It is specifically written to use Pascal functions and it encapsulates the CreateThread winapi call.
Let's take a look at the declaration and step through the parameters.
* SecurityAttributes: a pointer to a security record, used only in windows NT, fill in nil * StackSize: the initial stack size of the thread. The default value is 1MB. If you think this is too small fill in the desired size, otherwise if not fill in 0. * ThreadFunc: This is the function that will be executed while the thread is running. This is mostly a function with a while loop inside. The prototype is function(Parameter: Pointer): Integer * Parameter: This is a pointer to a parameter, which can be anything you like. It will be passed to the thread function as the parameter pointer. * CreationFlags: This flag determines whether the thread starts immediately or it is suspended until ResumeThread is called. Use CREATE_SUSPENDED for suspended and 0 for an immediate start. * ThreadID: This is a var parameter that will return the ThreadID of the thread.
Java's synchronize er ikke det samme som Delphi's synchronize...
Det er faktisk en smule besværligt det du er igang med. Men prøv at se dette eksempel.
En form, en listbox og en TIdTCPServer komponent sat til og lytte på port 9999. Du kan starte en cmd prompt og med kommandoen telnet 127.0.0.1 9999 kan du sende til den.
Nu har jeg brugt noget tid på at rode med det.. Men jeg kommer altså ikke rigtigt nogle vegne.
Jeg kan forstå at det er i min ServerExecute der skal ændres. Herunder er koden - kan I lede mig lidt mere på sporet (selvom jeg har fået meget hjælp allerede)?
procedure TForm1.TCPServerExecute(AContext: TIdContext); var Command: string; Command2: string; Index: Integer; begin Command:=Acontext.Connection.IOHandler.ReadLn; Command2:=Acontext.Connection.IOHandler.ReadLn;
// Load Profile if (Command='LP') then begin Index:=LogListBox.Items.IndexOf(AContext.Connection.Socket.Binding.PeerIP); If (Index>=0) then begin if (StrToInt(Command2)>=0) then begin LoadComboBox.ItemIndex:=StrToInt(Command2); Acontext.Connection.IOHandler.WriteLn('2'); LoadButton.Click; end;
if (StrToInt(Command2)<0) then begin Acontext.Connection.IOHandler.WriteLn('1'); end; end;
If (Index<0) then begin Acontext.Connection.IOHandler.WriteLn('0'); end; end; end;
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.