- Details
- Geschrieben von: Sebastian Jänicke
- Kategorie: Themen aus der Praxis
- Zugriffe: 41
Beim Debuggen des Fehlers "Cannot start a task that has already completed" bzw. "Eine bereits abgeschlossene Aufgabe kann nicht gestartet werden" ist mir aufgefallen, dass die korrekte Nutzung von TThread.CreateAnonymousThread und TTask.Run missverständlich sein kann. Die Benennung ist zwar schon korrekt, aber ich verstehe, weshalb jemand, der TThread.CreateAnonymousThread kennt, dies bei TTask.Run falsch macht.
Was ist passiert? Der Code sah so aus:
TTask.Run(procedure
begin
// etwas Code
end).Start;
Wenn man folgendes Pattern kennt, weiß man, weshalb derjenige das so geschrieben hat:
TThread.CreateAnonymousThread(procedure
begin
// etwas Code
end).Start;
Das Problem ist nur, dass TTask.Run so aussieht:
class function TTask.Run(const Func: TProc): ITask;
begin
Result := TTask.Create(Func, TThreadPool.Default);
Result.Start;
end;
Heißt:
Die Aufgabe wird direkt gestartet. Meistens dauert die Aufgabe lange genug, dass das eigene Start ausgeführt wird, bevor die Aufgabe beendet ist. Wenn dort aber eine Abbruchbedingung drin ist, kann es passieren, dass die Aufgabe da schon beendet ist. Dann kommt obige Fehlermeldung.
Ja, Run und Create... sind eindeutig benannt. Ich musste trotzdem mehrfach hinschauen, bis mir das aufgefallen ist.
- Details
- Geschrieben von: Sebastian Jänicke
- Kategorie: Themen aus der Praxis
- Zugriffe: 139
Ich werde immer wieder gefragt, was an with denn so schlimm sei. Nun, es gibt eine ganze Reihe von Gründen, weshalb man auf die Nutzung von with verzichten sollte. Diese möchte ich hier genauer erläutern.
-
Probleme mit neuen Delphiversionen
Der folgende Code ist kein besonders sinnvoller Code. Er soll nur kurz zwei Beispiele demonstrieren. Das sah in den VirtualTrees so aus (TStringEditLink.SetBounds), was zu einem Fehler unter XE2 führte.
procedure TMyForm.Test1;
var
TargetPos: TRect;
begin
TargetPos := Rect(0, 0, 500, 500);
with TargetPos do
begin
Width := Right - Left; // bis XE MyForm.Width, ab XE2 TargetPos.Width
Height := Bottom - Top;
end;
end;
procedure TMyForm.Test2;
var
TargetPos: TRect;
Offset: Integer;
begin
TargetPos := Rect(0, 0, 0, 0);
with TargetPos do
begin
Offset := 10; // bis XE Offset (lokale Variable), ab XE2 TargetPos.Offset
Self.Left := Left + Offset;
Self.Top := Top + Offset;
end;
end;
Unter Delphi XE führt der Aufruf von Test1 und Test2 dazu, dass das Formular die Position 10/10 und die Größe 500/500 einnimmt. Unter XE2 passiert mit Test1 gar nichts mehr und Test2 lässt sich nicht mehr kompilieren.
Zur Erklärung:
Mit Delphi XE2 wurde TRect unter anderem um Width und Offset erweitert. Die Zuweisung an Width passiert daher nun in TargetPos, wo es keinen Effekt hat. Und da TargetPos nun die Methode Offset hat, verdeckt diese die lokale Variable, so dass der Code nicht mehr kompiliert werden kann.
Der zweite Fall fällt nach einem Update der Delphiversion natürlich auf. Der erste Fall jedoch bleibt ggf. unentdeckt und kann dann zu schwer lokalisierbaren Fehlern führen. Im echten Code sind die Auswirkungen ja nicht unbedingt so direkt nachvollziehbar. Und solche Änderungen können überall auftreten, so dass with ein unkalkulierbares Risiko ist.
-
Probleme beim Debuggen
procedure TMyForm.Test;
var
Demo: TPoint;
begin
with Demo do
begin
X := 42;
X := X + 1; // Haltepunkt
end;
end;
Wenn man den Wert von X wissen möchte, kann man normalerweise einfach den Mauszeiger darüber halten. Mit with geht das leider nicht. Markiert man nun den Ausdruck und drückt Strg + F7, geht das auch nicht. Man muss also manuell den Ausdruck aus with kopieren und in das Fenster zum Auswerten kopieren (oder ihn manuell ergänzen).
Dadurch wird das Debuggen unnötig umständlich.
-
Schlechtere Lesbarkeit des Codes
Man sieht nicht auf den ersten Blick, welcher Identifier zu dem Element in with gehört und welcher eigenständig ist, was die Lesbarkeit erschwert. Wenn man selbst in einem Jahr auf den Quelltext schaut oder gar jemand anderes, braucht das Verständnis des Quelltextes entsprechend länger.
Außerdem braucht man zum Verständnis des Codes immer die genauen Variablentypen, da man sonst nicht weiß, welche Elemente diese haben. Und wenn man die Elemente nicht im Kopf hat, muss man das erst recherchieren. Dadurch dauert es deutlich länger, Code zu überblicken und zu ändern.
-
Refactoring des Quelltexts
Man kann Teile des Quelltextes nicht einfach kopieren oder überarbeiten, weil man auf das with Rücksicht nehmen muss.
-
LSP und Codevervollständigung
Der LSP und damit die Codevervollständigung funktionieren mit with schlechter.