Zum Inhalt springen

Empfohlene Beiträge

Geschrieben

Hallo liebe Community,

ich habe ein Problem bei der Thread-Programmierung.

Ich starte mehrere Threads, viele dieser gestarteten Threads melden sich

ordnungsgemäß ab (über Synchronize). Doch es gibt einige schwarze

Schafe welche diese "Bin-Fertig"-Routine nicht durchlaufen, obwohl

es keinen Umweg gibt.

Den Thread starte ich so:


(* viel Code vorher *)
(* Neuen Thread erzeugen und in Array merken*)
AktivThreads[idxAktivThread] := TDownload.Create(
(* Erster Parameter : Speicherort *)
linkSaveName,
(* Zweiter Parameter : Quelle *)
link,
(* Dritter Parameter : Zusätzlich Identifikations#
da ThreadID doppelt vorkommt *)
threadUID,
(* Vierter Parameter : Create Suspended *)
True
);
(* viel Code nachher *)
[/PHP]

Der Thread selber sieht so aus:

[PHP]
unit Download;

interface

uses
Classes, UrlMon;

type
TDownload = class(TThread)
public
constructor Create(pSpeichort: String; pLink: String;
pUniqueID: String; CreateSuspended : BOOLEAN = TRUE);
private
protected
procedure Execute; override;
(* Synchronisierter Zugriff auf Hauptthread *)
procedure AusListeEntfernen;

var Speicherort: String;
SourceFile: String;
UniqueID: String;
end;

implementation

(* Code ist das Hautpprogramm *)
uses Code;

(* Überladene Create-Funktion zur Variablen Übergabe *)
constructor TDownload.Create(pSpeichort: String; pLink: String;
pUniqueID: String; CreateSuspended : BOOLEAN = TRUE);
begin
Inherited Create(CreateSuspended);

Speicherort := pSpeichort;
SourceFile := pLink;
UniqueID := pUniqueID;

end;

procedure TDownload.AusListeEntfernen();
begin
Form1.ThreadEntferneAusListe(Self.ThreadID, UniqueID);
end;

procedure TDownload.Execute;
begin
try
UrlDownloadToFile(nil, PChar(SourceFile), PChar(Speicherort), 0,nil);
finally
(* keine Aktion nötig, soll nur nicht abstürzen bei Fehler *)
end;

Synchronize(AusListeEntfernen);
end;

end.

so und jetzt noch die Synchronisierte Funktion im Haupt-Thread:


procedure TForm1.ThreadEntferneAusListe(ID: Cardinal; UID: String);
var index : byte;
ThreadFound : Boolean;
begin
ThreadFound := False;

(* Array Durchlaufen und entsprechenden Thread finden *)
for index := 1 to maxDownloadThreads do
begin
if (AktivThreads[index].ThreadID = ID) and
(UniqueIDs[index] = UID) then
begin
AktivThreads[index] := nil;
Protokoll.Lines.Add('Thread #' + IntToStr(index) +
' mit ID ' + IntToStr(ID) + ' beendet.');

(* weiterer kurzer Code um Ereignis sichtbar zu machen *)

ThreadFound := True;
end;
end;

if not ThreadFound then
begin
Protokoll.Lines.Add('**SPEICHERLECK** Thread' +
' mit ID ' + IntToStr(ID) + ' nicht gefunden!');
Sleep(500);
end;
end;
[/PHP]

Die Meldung mit dem Speicherleck hatte ich noch nie!

Das heißt eigentlich das alles Threads immer Ordnungsgemäß funktionieren müssten, leider "verliert" das Programm Threads. Sie melden sich einfach nicht

ab, obwohl er fertig sein müsste.

Ich weiß nicht wie das Problem entsteht, vllt. habe ich einen Denkfehler oder etwas falsch implementiert.

Ich hoffe Ihr könnt mir einen Tipp geben an welcher Stelle ich etwas besser machen kann/sollte/muss :).

Danke vorab!

Geschrieben

Hmmm, threads in Delphi sind schon eine Weile her... ich versuch es mal.

Erstmal, was mit gleich aufgefallen ist:


type

  TDownload = class(TThread)

  public

    constructor Create(pSpeichort: String; pLink: String;

                       pUniqueID: String; CreateSuspended : BOOLEAN = TRUE);

  private

  protected

    procedure Execute; override;

    (* Synchronisierter Zugriff auf Hauptthread *)

    procedure AusListeEntfernen;


    var Speicherort: String;

        SourceFile: String;

        UniqueID: String;

  end; 

var im protected? Willst du deine Threads weitervererben? Oder die Variablen irgendwem sonst zugänglich machen? Denke doch nicht. Also solltest du die ganz normal als Felder im Private des threads deklarieren:

type

  TDownload = class(TThread)

  public

    constructor Create(pSpeichort: String; pLink: String;

                       pUniqueID: String; CreateSuspended : BOOLEAN = TRUE);

  private

     Speicherort: String;

     SourceFile: String;

     UniqueID: String;

  protected

    procedure Execute; override;

    (* Synchronisierter Zugriff auf Hauptthread *)

    procedure AusListeEntfernen;

  end; 

Und ohne var davor. Weiter die Einbindung von Form1. :eek Ich hab jetzt keine Delphi-Hilfe mehr da oder sonst was, aber sowas ist ein abolutes NoGo. Zum einen beziehst du dich direkt auf den Formnamen (wobei form1 auch nicht form1 heißen sollte, aber egal ;) ). Wenn du den mal änderst? Oder wenn du die Threads in einem anderen Programm verwenden willst? Wenn du ein zweites Formulas hinzufügst, was dann den Threadaufruf startet? Der Sinn der modularen Programmierung ist ja, dass die Module (die einzelnen Units) recht frei verwendet werden können, auch in anderen Programmen. Genau das verhinderst du ja hier, weil du alles von deinem Hauptformular abhängig machst. Also: Die Prozedur AusListeEntfernen entfernen. Das uses Code entfernen. In der Objektorientierung gibt es etwas wunderbares, nämlich die Events. Ein TThread bietet auch ein paar Events, unter anderem das OnTerminate-Event. Das wird genau dann abgefeuert, wenn der Thread sich beendet. Das solltest du dann auch nutzen. Ich kenn die genaue Signatur des Events nicht mehr, aber die Hilfe sagt dir da ne Menge. (Hab ich schonmal gesagt, dass ich die Delphi Hilfe liebe? Ich hab noch keine Hilfe gesehen, die so ausführlich und gut ist wie die von Delphi. Weder Visual Studio, MSDN, noch Java oder ähnliches haben auch nur annähernd so eine gute Hilfe wie Delphi!) Falls du das noch nie gemacht hast hier eine kleine Erklärung: Einem Event kannst du eine Prozedur zuweisen, die dann ausgeführt wird, wenn das Event aufgerufen wird. Die Prozedur kann irgendeine sein, sie muss nur dieselben Parameter haben wie die geforderten. du kennst es vielleicht, wenn du einen Doppelklick auf einen TButton machst in Delphi. Dann legt Delphi eine ButtonClick Prozedur an und weißt die auch gleich dem Event zu. Das machst du hier auch, nur händig.

// Hauptprogramm

// viel Code vorher


// OnTerminate Event für Threads

// Die Parameter weiß ich nicht mehr)

procedure ThreadTerminate(Sender: TObject;)

begin

  // Das tun was getan werden soll, wenn der Thread beendet wird

end;


  (* viel Code vorher *)

  (* Neuen Thread erzeugen und in Array merken*)

  AktivThreads[idxAktivThread] := TDownload.Create(

  (* Erster Parameter : Speicherort *)

       linkSaveName,

  (* Zweiter Parameter : Quelle *)

       link,

  (* Dritter Parameter : Zusätzlich Identifikations# 

                                da ThreadID doppelt vorkommt *)

       threadUID,

  (* Vierter Parameter : Create Suspended *)

       True

  );

  // Terminate-Event zuweisen

  AktivThreads[idxAktivThreads].onTerminate:= ThreadTerminate;

  (* viel Code nachher *)  


Somit wird deine Prozedur aufgerufen, sobald der Thread sich beendet. Was das Problem bei deinem Code sein kann:

procedure TDownload.Execute;

begin

  try

    UrlDownloadToFile(nil, PChar(SourceFile), PChar(Speicherort), 0,nil);

  finally

    (* keine Aktion nötig, soll nur nicht abstürzen bei Fehler *)

  end;


  Synchronize(AusListeEntfernen);

end; 

Wenn UrlDownload einen Fehler Produziert, wird AusListeEntfernen nicht aufgerufen! Das solltest du in finally schreiben, dass das also auf jeden Fall aufgerufen wird. Und um einen Fehler abzufangen musst du eh Try Except benutzen ;)

Geschrieben
Hmmm, threads in Delphi sind schon eine Weile her... ich versuch es mal.

Erstmal, was mit gleich aufgefallen ist:

... * code aus Platzgründen entfernt * ...

var im protected?

Willst du deine Threads weitervererben? Oder die Variablen irgendwem sonst zugänglich machen? Denke doch nicht.

Also solltest du die ganz normal als Felder im Private des threads deklarieren:

Ok, das habe ich wie vorgeschlagen korrigiert. Hierzu habe ich keine Einwände weil es einfach so stimmt :)

... * code aus Platzgründen entfernt * ...

Und ohne var davor.

Weiter die Einbindung von Form1. :eek

Ich hab jetzt keine Delphi-Hilfe mehr da oder sonst was, aber sowas ist ein abolutes NoGo.

Zum einen beziehst du dich direkt auf den Formnamen (wobei form1 auch nicht form1 heißen sollte, aber egal ;) ). Wenn du den mal änderst? Oder wenn du die Threads in einem anderen Programm verwenden willst? Wenn du ein zweites Formulas hinzufügst, was dann den Threadaufruf startet?

Der Sinn der modularen Programmierung ist ja, dass die Module (die einzelnen Units) recht frei verwendet werden können, auch in anderen Programmen. Genau das verhinderst du ja hier, weil du alles von deinem Hauptformular abhängig machst.

Soweit habe ich mir noch keine Gedanken gemacht ob ich die Unit wirklich in anderen Projekten verwenden möchte. Ich arbeitet zurzeit an der fünften Version des Programms. Die Threads sind in dieser Version neu weil ich so mehrere Aufgaben gleichzeitig verarbeiten kann ohne auf "Wartezeiten" andere Faktoren Rücksicht nehmen zu müssen.

Also:

Die Prozedur AusListeEntfernen entfernen. Das uses Code entfernen.

In der Objektorientierung gibt es etwas wunderbares, nämlich die Events. Ein TThread bietet auch ein paar Events, unter anderem das OnTerminate-Event.

Das wird genau dann abgefeuert, wenn der Thread sich beendet.

Das solltest du dann auch nutzen.

Ich kenn die genaue Signatur des Events nicht mehr, aber die Hilfe sagt dir da ne Menge. (Hab ich schonmal gesagt, dass ich die Delphi Hilfe liebe? Ich hab noch keine Hilfe gesehen, die so ausführlich und gut ist wie die von Delphi. Weder Visual Studio, MSDN, noch Java oder ähnliches haben auch nur annähernd so eine gute Hilfe wie Delphi!)

Falls du das noch nie gemacht hast hier eine kleine Erklärung:

Einem Event kannst du eine Prozedur zuweisen, die dann ausgeführt wird, wenn das Event aufgerufen wird. Die Prozedur kann irgendeine sein, sie muss nur dieselben Parameter haben wie die geforderten. du kennst es vielleicht, wenn du einen Doppelklick auf einen TButton machst in Delphi. Dann legt Delphi eine ButtonClick Prozedur an und weißt die auch gleich dem Event zu. Das machst du hier auch, nur händig.

... * code aus Platzgründen entfernt * ...

Somit wird deine Prozedur aufgerufen, sobald der Thread sich beendet.

Wenn ich das mit OnTerminate erledige, was durchaus auch in Ordnung wäre habe ich keine Möglichkeit mehr im Hauptformular das Ereignis zu visualisieren.

Bisher konnte ich genau herausfinden welcher Thread fertig war und welcher noch nicht. Hierzu ist eben der Griff über "Form1" nötig.

Signatur : OnTerminate(Sender: TObject)

Was das Problem bei deinem Code sein kann:

... * code aus Platzgründen entfernt * ...

Wenn UrlDownload einen Fehler Produziert, wird AusListeEntfernen nicht aufgerufen! Das solltest du in finally schreiben, dass das also auf jeden Fall aufgerufen wird. Und um einen Fehler abzufangen musst du eh Try Except benutzen ;)

Ich dachte immer das alles nach end des finally auch durchlaufen wird.

Werde diese Fehlerquelle korrigieren.

Werde nochmal einen Langzeittest durchführen um zu sehen ob das Programm immer noch Threads verliert oder ob es sich gebessert hat.

Danke für bereits geleistet Unterstützung.

Geschrieben

Wenn ich das mit OnTerminate erledige, was durchaus auch in Ordnung wäre habe ich keine Möglichkeit mehr im Hauptformular das Ereignis zu visualisieren.

Bisher konnte ich genau herausfinden welcher Thread fertig war und welcher noch nicht. Hierzu ist eben der Griff über "Form1" nötig.

Natürlich kannst du das im Hauptformular visualisieren und auch sonst alles tun was du willst.

OnTerminate nimmt eine prozedur auf, und die kann kommen von wo sie will. Die ThreadTerminate-Prozedur in meinem Beispiel sollte eben im Form1 implementiert sein, das hab ich nur nicht dazugeschrieben, da ich dachte das wäre klar ;)

Solch eine Verschränkung wie bei dir ist unabhängig vom Modularen schon schlecht. Das ist einfach ein schlechter Stil. Mehr kann ich dazu im Moment gar nicht sagen, es ist einfach... schlecht das allein zu sehen :D

Solche Events sind genau dafür da, dass du von außen auf irgendwas in Objekten reagieren kannst. Ohne dass das Außen irgendwas mit dem Objekt zu tun haben muss.

Im Prinzip macht das ja nichts anderes als das was du getan hast. Ich weiß nicht ob du schonmal selber ein Event programmiert hast, sieht aber nicht so aus.

Also:

Ein Event besteht erstmal aus einem Prozedurzeiger, der ist erstmal leer.

Wenn du dem Event eine Prozedur zuweißt, dann steht diese da im Zeiger.

Kommt der Code jetzt an diese Stelle wo ein Event ausgelöst werden soll, dann geschieht das, indem geprüft wird, ob der Zeiger leer ist, und wenn nicht, wird die Prozedur auf die der Zeiger verweißt aufgerufen.

Also ganz einfach ung so:



//*** Hier steht u.U. bissl Code

type

TThreadTerminateEvent = procedure(Sender: TObject);


//*** Hier ist die Klasse definiert

TThread = Class(Thread) // Keine Ahnung wie die genau aussieht

...

private

//*** hier der Prozedurzeiger

FThreadTerminate: TThreadTerminate;

...

public

//*** hier sind die Events als public definiert

 OnTerminate: TThreadTerminate; read FThreadTerminate; write FThreadTerminate; 

// Denke das war so... kannst dir ja aber den Quelltext anschauen 

// wenn du willst

...

end;


//*** eine Menge mehr Code


//*** jetzt passiert was und ich als Entwickler des Objekts will, 

//*** dass man das von außen mitbekommen kann

//*** in dem Beispiel ist der Thread beendet

if (assigned(FThreadTerminate)) then

begin

  FThreadTerminate(self);

end;

...

Du kannst also einfach den Code deiner Prozedur in deine ThreatTerminate Prozedur kopieren und alles geht gehauso wie vorher... den Thread bekommst du über den Sender, musst ihn u.U. halt casten vor dem benutzen

z.B.

(Sender as TThread).ThreadID

Und wenn du nicht sicher bist, dass das immer ein TThread ist noch eine Prüfung If Sender is TThread davor.

EDIT:

Natürlich könntest du auch das ganze überschreiben und dein eigenes Event als OnTerminate ausführen... da könntest du dann wohl deine ThreadID oder was auch immer als Parameter an die Prozedur übergeben.

Also ein Event ist im Grunde nichts anderes als das was du gemacht hast, nur eben Source-unabhängig, weil du die aufgerufene Prozedur erst zur Laufzeit da reinkopierst.

Geschrieben

Noch was:

Ich dachte immer das alles nach end des finally auch durchlaufen wird.

Nein, gerade das wird ja nicht durchlaufen. Ansonsten würde das ja keinen Sinn machen.

Wenn ein Fehler auftritt, wird die Ausführung normal an der Stelle abgebrochen. Alles was danach steht wird nie aufgerufen.

Aus dem Grund gibt es try finally. Im Try block ist der Code, der Probleme bereiten könnte und im Finally Block ist das, was auch wenn ein Fehler auftritt, auf jeden Fall vor dem Rücksprung noch ausgeführt werden soll. Und nur der Finally Block wird dann ausgeführt vor dem Abbruch.

EDIT:

Wenn du das mit einem Try Except machst so wie du das mit dem Finally hast bei dir, dann würde der Code ausgeführt werden. Dabei würdest du aber den Fehler einfach verschlucken und nicht auf ihn reagieren und nichts.

Geschrieben

Hallo JesterDay,

erstmal danke für eine ausführlichen Antworten.

Natürlich kannst du das im Hauptformular visualisieren und auch sonst alles tun was du willst.

Das dachte ich mir schon fast das das geht, nur weiß _ich_ nicht wie das geht.

Hätte mich vllt. anders ausdrücken sollen.

  • 2 Wochen später...
Geschrieben

Hallo zusammen,

ich konnte mich leider nicht eher melden, dafür hab ich aber was mitgebracht :cool:

Ich denke das Problem mit den verschollenen Threads habe ich behoben.

Habe mir hierzu ein kleines Programm geschrieben, um die Thread-Thematik ohne großes "Drumherum" zu analysieren.

Da ich das Programm geschrieben habe, wird auch mein "unschöner" Code verwendet.

Damit ich aber auch hier etwas dazulernen kann, bitte ich euch das Ihr über mein Programm schaut und mir dann mitteilt wie man das "schöner" lösen könnte.

Schon einmal vielen Dank vorab.

Ich bitte um Verständnis das ich zum Thema Programmierstil in diesem Fall kein neues Thema eröffne, da es sonst aus dem Zusammenhang gerissen werden wird.

MultiThread.zip

Geschrieben

Guten Abend,

habe mir jetzt den ganzen Nachmittag bis jetzt das Programm angesehen und

habe zur Vereinfachung ein neues geschrieben mit den wesentlichen Verbesserungen.

Die neue Version befindet sich im Anhang.

Nachdem die Syntaxfehler behoben waren, die man automatisch macht wenn

man den Code nur im "Windows Editor" schreibt bekam ich noch echte

"Probleme".

Ich erhielt ständig Zugriffsverletzungen im Speicher. Ich kam nicht dahinter

wie/wo diese entstehen bzw. es mir unerklärlich war, habe ich kurzerhand

einfach Version 2.0 erstellt. Selbstverständlich mit den entsprechenden

Verbesserungen und sogar ein wenig den Stil beachtet :D

Es kam dann zu folgenden Problemen:

Das Programm ist immer abgestürzt als es den FreeAndNil-Befehl durchführte.

Deswegen habe ich diesen dann entfernt :(, hörte sich von der Definition nach

nicht schlecht an. Wobei ich sowieso nicht sicher bin ob es

FreeOnTerminate := True und FreeAndNil() gemeinsam bringen.

Vielleicht weiß jemand (*schiel zu JesterDay*) mehr :P

Das nächste war von der Grundfunktion her:

Bei meiner Version konnte man nachdem man 12 Threads gestartet hatte und

diese fertig waren, erneut 12 Threads starten. Dies ging in der neuen Version

nicht mehr da hier das Array dynamisch erweitert wurde mit SetLength().

Es war z.B. möglich wenn 12 Threads liefen und einer davon fertig war, man

sofort wieder einen "nachstarten" konnte, weil ja wieder ein Platz frei wurde

von den maximal 12 :).

Da war meine Version dann doch einfacher umzusetzen :) mit for.to.do

-> Hab aber trotzdem Teile übernommen, jetzt muss man nur eine Konstante

ändern um das Array zu vergrößern/verkleinern. (Böse Falle ist weg :D )

Sonst hat bei Version 2.0 alles gepasst :)

Ich wusste z.B. das Variablen in der OnTerminate-Prozedur nicht mehr

verfügbar sind, weswegen ich dies umständlich gelöst habe. Durch den

modifizierten Code habe ich festgestellt das das nicht für "Properties" gilt.

Somit konnte ich den Event OnTerminate des Threads verwenden.

Ich hab diesmal sogar Kommentare hinterlassen :)

Sind zwar noch nicht so gut wie die von JesterDay aber die Übung macht den

Meister :)

Vielen Dank JesterDay. :uli

MultiThread2.0.zip

Geschrieben
Wobei ich sowieso nicht sicher bin ob es

FreeOnTerminate := True und FreeAndNil() gemeinsam bringen.

Vielleicht weiß jemand (*schiel zu JesterDay*) mehr :P

Wenn ich das gewußt hätte, dann hät ich dazu was gesagt oder das verbessert. Sagte ja im Kommentar (glaub ich doch), dass es da zu Problemen kommen kann. Der Thread ist noch nciht fertig und wird schon freigegeben... du sägst dir den Ast ab auf dem du sitzt.

Zu FreeOnTerminate... da kann die die Hilfe dazu alles sagen was du wissen musst und was genau das bewirkt.

Jetzt mal ernsthaft. Die Delphi Hilfe hilft einem wirklich. Ich denke ja mal, dass die nicht schlechter geworden ist seit Version 7 (und allen davor, bis 4 kenn ich die).

Da steht dann drin, was genau das tut und ob der Zeiger auch auf nil gesetzt wird o.ä. Wenn nicht, was ja ansich kein Fehler ist, darfst du halt nicht mit <> nil prüfen ;) Aber das hat ja scheinbar funktioniert, also wird da wohl was getan... wie auch immer (siehe Ast und absägen).

Also die Delphi Hilfe hilft dir bestimmt so gut wie ich. Da hab ich auch viel gelernt.

Zu den anderen Problemen... naja, solche konnt ich natürlich nich direkt erkennen und am Design der Anwendung musst du schon selber basteln. Das geht nur mit nem Texteditor schwer.

Mit den Threads neu starten... ja, stimmt. Das is bei mir nich so gut.

Mal als idee:

Du könntest dir die vergebenen Indizes in einem Set merken. Ein Set kannst du einfach mit "i in Set" prüfen ob es eine Zahl enthält. Hinzufügen und Entfernen geht glaub auch einfach, musst aber in der Hilfe nachsehen.

var usedIndizes: Set of Byte;

Byte = Integer nur beschränkt auf 0..255 (also praktisch dasselbe wie integer). BRaucht halt nur 1/4, eben 1 Byte.

Beim beenden löschst du den index daraus, beim hinzufügen ...hmmmm...

ich glaub aber eine for to do mit in geht schneller... auch wenn es auch immer länger dauert.

Naja, vielleicht ist die idee für mehrfach wiederverwendbare nicht die beste...

Werd mir das nochmal ansehen... wenn ich zeit hab, im Moment hab ich da keinen kopf für, vielleicht fällt mir dann auch was ein.

Geschrieben

Hallo JesterDay,

ich wollte Dir in keiner Weise zu nahe treten, ich bin doch froh das Du Dir das

überhaupt ansiehst. Es existiert kein Zeitdruck oder ähnliches, es dient ja als

Lern Beispiel wie man mit Threads umgehen kann.

Ich werde versuchen mit der Delphi-Hilfe besser klar zu kommen.

Genauso wenig drehe ich dir einen Strick daraus wenn da etwas im Programm

nicht funktioniert hat. Das meine Eigenarbeit weiterhin erforderlich ist habe ich

nicht ausgeschlossen. Ich dachte ich schreib Dir welche Probleme noch

aufgetreten sind da ich deine Vorschläge verwendet habe.

Also die Delphi Hilfe hilft dir bestimmt so gut wie ich. Da hab ich auch

viel gelernt.

Die Hilfe wird niemals einen Menschen ersetzen können :), auch wenn sie noch

so gut ist.

Werde mir das mit der "Set of"-Thematik mal ansehen.

Geschrieben
Hallo JesterDay,

ich wollte Dir in keiner Weise zu nahe treten, ich bin doch froh das Du Dir das

überhaupt ansiehst.

Das hab ich auch nicht so verstanden gehabt.

Naja, der Mensch ist dann halt noch ne Art Suchmaschine für die Hilfe, denn er kann erkennen was du eigentlich willst. Bei der Hilfe musst du schon selber suchen ;)

Aber so allgemein zu Themen wie "Vielleicht weiß ja wer was der Unterschied zw. FreeOnTerminate und FreeAndNil ist" findest du da sehr viel. Nur musst du es u.U. halt selbt korrekt interpretieren.

Dennoch kann ich nicht oft genug erwähnen, dass die Delphi Hilfe Klasse ist.

MSDN kommt dann wohl mit etwas Abstand auf Rang 2, weil sie es zwar ähnlich versuchen, aber mehr als ein Versuch ist es nicht da ranzukommen. Java Hilfe ist eigentlich nur ne Klassenbeschreibung. Bei MSDN gibt es wenigstens ein paar Beispiele, die aber meist total daneben sind und mit dem was man sucht nur wenig zu tun haben.

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...