Laden...

Control.Invoke auch bei Lesezugriffen nötig?

Erstellt von Aschratt vor 15 Jahren Letzter Beitrag vor 15 Jahren 2.531 Views
Aschratt Themenstarter:in
97 Beiträge seit 2007
vor 15 Jahren
Control.Invoke auch bei Lesezugriffen nötig?

Tach,

ich habe ein Programm geschrieben, welches Daten aus einem seperaten Thread in eine ListView schreibt. Bis dahin ja kein Problem, allerdings möchte ich jetzt ebenfalls über einen seperaten Thread die Daten aus der ListView wieder auslesen. Meine Frage also: Muss ich für einen Lesezugriff (Beispielsweiße auf ein ListView.ListViewItem) auch einen Invoke aufrufen, oder kann ich für Lesezugriffe direkt auf das Control zugreifen (Bsp: _lvMyListView.Items[...].SubItems[...].Text)?

Danke schonmal.

  • Aschratt.
Gelöschter Account
vor 15 Jahren

Muss ich für einen Lesezugriff (Beispielsweiße auf ein ListView.ListViewItem) auch einen Invoke aufrufen, oder kann ich für Lesezugriffe direkt auf das Control zugreifen (Bsp: _lvMyListView.Items[...].SubItems[...].Text)?

Ja. der zugriff auf ein string ist nciht atomar. das bedeutet, das der string nicht in einem einzien vorgang gelsen werden kann. somit kann der zugriff selbst mitten drinn unterbrochen werden und die quelle verändert werden.

eine synchronistation ist daher zwingend notwendig.

Aschratt Themenstarter:in
97 Beiträge seit 2007
vor 15 Jahren

Alles klar, danke! 👍

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo Aschratt,

es ist nicht nur eine Frage der Synchronisation. Windows-Forms ist vom Design her single-threaded, d.h alle Zugriffe auf Controls müssen immer aus dem Thread aus erfolgen, der das Control erzeugt hat. Unabhängig davon, ob im speziellen Zugriffsszenario eine Synchronisation erforderlich wäre oder nicht. Das heißt du muss auch beim Lesen (z.B. eines Int-Werts aus einem Control) Control.Invoke verwenden.

herbivore

3.971 Beiträge seit 2006
vor 15 Jahren

Hallo JAck30lena,

Ja. der zugriff auf ein string ist nciht atomar. das bedeutet, das der string nicht in einem einzien vorgang gelsen werden kann. somit kann der zugriff selbst mitten drinn unterbrochen werden und die quelle verändert werden.

Ist so nicht ganz richtig, das Kopieren eines Zeigers (Adresse der Variable) selbst ist atomar. Genauso das Kopieren eines Werttypes (ein Zeiger ist im Grunde ein Werttyp).

Allerdings ist es bei mehreren CPU-Kernen möglich, dass sich der aktuelle Wert noch im Cache eines Kerns befindet, im Arbeitsspeicher selbst aber noch ein alter Wert sich befindet. Beim Zugriff ohne lock würde dann aber nur der alte Wert gelesen werden und nicht der von Kern x geänderte Wert. Dies tritt aber nur bei mehreren CPUs auf. Lock, WaitHandle, Interlocked, usw. sorgen dafür, dass beim Zugriff auf die entsprechenden Variablen erst der Cache der jeweiligen Kerne zurück in den Speicher geschrieben werden, bzw. sorgen dafür, dass bereits im Cache enthaltene Werte vom Arbeitsspeicher aktualisiert werden.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Gelöschter Account
vor 15 Jahren

hallo kleines_eichhoernchen,

erstmal danke für die erläuterung. das habe ich nicht gewusst bzw. hatte ich eine andere vorstellung davon. du sagst das werttypen immer atomar verarbeitet werden. wie verhält sich das dann bei einem großen struct? und wie kann ein 32 bit system ein z.b. dezimal mit 96 bit atomar lesen?

ist es z.b. so das es anweisungsblöcke gibt, die nicht unterbrochen werden dürfen oder wie ist das zu verstehen?

gruß
Jack

3.971 Beiträge seit 2006
vor 15 Jahren

Gut das du nachfragst, hätt ich jetzt ohne nachzuschauen direkt falsch beantwortet.

“Reads and writes of the following data types shall be atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.”

(aus K. Scott Allen - Atomic Operations.

Beim Kopieren von Werttypen werden die Member Variablen nicht selbst einzeln kopiert, sondern der komplette Speicherbereich des Werttypes wird Bit für Bit kopiert. Das Kopieren selbst erfolgt atomar für jeweils die Größe eines Int-Datentyps

What is an atomic operation in the CLR? Partition I of the CLI specification states that:
"A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size (the size of type native int) is atomic…”.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Gelöschter Account
vor 15 Jahren

also doch so wie ich es mir dachte. da bei einer stringübergabe der string selbst kopiert wird, ist es kein atomarer vorgang.
in einem 32 bit system ist alles bis 32 bit atomar (int uint und alles darunter) und in einem 64 bit system ist alles bis 64 bit atomar (long, double, int, uint und alles darunter). struct usw ist in der regel immer nciht atomar.

3.971 Beiträge seit 2006
vor 15 Jahren

da bei einer stringübergabe der string selbst kopiert wird, ist es kein atomarer vorgang.

String ist ein immutable Referenztyp. Hier wird nur der Zeiger-kopiert und das ist atomar.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Gelöschter Account
vor 15 Jahren

es muss eine kopie des strings entstehen. wo bzw wann passiert dann das?

3.971 Beiträge seit 2006
vor 15 Jahren

Strings sind immutable, können daher nicht geändert werden. Bei einer Übergabe an eine Funktion (by value) wird nur die Instanz (der Zeiger auf die String-Klasse) übergeben. Wenn jedes mal der komplette String kopiert werden müsste (Unicdode, UTF 16) wären normale Anwendung mit heutigen Rechnern gerade so lauffähig.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Aschratt Themenstarter:in
97 Beiträge seit 2007
vor 15 Jahren

Bei einer Übergabe byValue wird nicht die Instanz übergeben, sondern eine auf dem Aufrufstack erzeugte Kopie des Objektes. Eine Instanz wird nur byRef übergeben. Ist ja auch logisch:

// Call byValue
string myString = "Hello World";
workWithString(myString);
// myString is now still "Hello World"

private void workWithString(string str)
{
    str = "Goodbye World";
}

// Call byReference
myString = "Hello World";
workWithStringRef(ref myString);
// myString is now "Goodbye World";

private void workWithStringRef(ref string str)
{
    str = "Goodbye World";
}

In C# müssen byRef calls über das ref-Schlüsselwort getätigt werden (Anders als bei C++, wo man einen Pointer übergeben würde). Das veranlasst das .NET Framework dazu eine Referenz auf ein Objekt zu übergeben. Alle anderen Aufrufe sind automatisch byValue und arbeiten mit einer Kopie des Objekts.

Wenn ich das also richtig verstanden habe, dann...

workWithStringRef(ref _lvMyListView.Items[...].SubItems[...].Text);    // ... ist atomar, weil die Referenz architekturunabhängig immer einer atomaren Größe entspricht (Maschinenwort)
workWithString(_lvMyListView.Items[...].SubItems[...].Text);    // ... ist nicht atomar, weil eine Kopie des gesamten Strings übergeben wird.

Grüße!

Gelöschter Account
vor 15 Jahren

so wie ich das verstanden habe, dann hättest du in deinem beispiel beim datentyp decimal z.b. recht aber nciht beim datentyp string. string ist im framework eine sonderstellung. eigendlich ist string ein referenztyp, der aber ähnlich einem werttyp behandelt wird.

Aschratt Themenstarter:in
97 Beiträge seit 2007
vor 15 Jahren

so wie ich das verstanden habe, dann hättest du in deinem beispiel beim datentyp decimal z.b. recht aber nciht beim datentyp string. string ist im framework eine sonderstellung. eigendlich ist string ein referenztyp, der aber ähnlich einem werttyp behandelt wird. Der Typ "string" korrespondiert doch zum C++ Typ std::string, welcher wiederum lediglich ein "const char*" ist. Ein Pointer hat die Größe eines Maschinenwortes, also müsste eine Referenz eines Strings, genau wie ein C++ Pointer auf ein char-Array ebenso die Größe eines Maschinenwortes haben, oder nicht?!

Gelöschter Account
vor 15 Jahren

ich meinte das:

workWithStringRef(ref _lvMyListView.Items[...].SubItems[...].Text);    // ... ist atomar, weil die Referenz architekturunabhängig immer einer atomaren Größe entspricht (Maschinenwort)  
workWithString(_lvMyListView.Items[...].SubItems[...].Text);    // ... ist nicht atomar, weil eine Kopie des gesamten Strings übergeben wird.  

das scheint so nciht zu stimmen, da im 2. falle nicht die kopie sondern die referenz übergeben wird. das ändern des strings ist aber nciht atomar, da dabei ein komplett neuer string in einem komplett anderen speicherbereich erzeugt wird.

3.971 Beiträge seit 2006
vor 15 Jahren

Das Lesen oder Schreiben eines Decimal ist keine atomare Operation, da der verwendete Speicher von Decimal größer ist als 32/64 Bit (Integer). Das Auslesen oder die Zuweisung eines Strings ist hingegen eine atomare Operation, weil jedeglich die Adresse des Strings kopiert werden muss, nicht aber sein Inhalt, genauso wie bei jedem anderen Referenztyp.

das ändern des strings ist aber nciht atomar, da dabei ein komplett neuer string in einem komplett anderen speicherbereich erzeugt wird.

Genau. Jede Funktion von System.String, die scheinbar ein oder mehrere Zeichen ändert, gibt eine neue Instanz mit den geänderten Zeichen zurück (Immutable). Die Zuweisung selbst (anders als bei Werttypen) ist nur ein Umkopieren des Zeigers. Bei Werttypen selbst muss bei einer Zuweisung der komplette Speicher der Struktur umkopiert werden.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Aschratt Themenstarter:in
97 Beiträge seit 2007
vor 15 Jahren

Also ist eine Übergabe byValue nur Scheinbar byValue, weil in Wirklichkeit nur die Referenz des Objekts übergeben wird, und im Falle eines returns umgeschrieben wird? Und byReference hingegen wird die Referenz zum bearbeiten des Objektes verwendet?

Zur Typatomarität (Wie auch immer das ausgeprochen wird 😄): Operationen über einem Typ sind nur atomar, wenn der Typ kleiner oder gleich der Größe eines Maschinenwortes ist (int, uint, char,...), aber Operationen über der Referenz eines Typs sind immer atomar? (Damit hätte JAack30lena recht und das 2. Beispiel wäre eine atomare Operation)

Um aber wieder zum Thema zurück zu kommen: Auf meinem Singlecore System hier wirft mir der Debugger mit einem Integer-typ, den ich Threadübergreifend ohne Control.Invoke abfrage keinen Fehler aus, allerdings beim Zugriff auf einen String streikt er. 🤔

Gelöschter Account
vor 15 Jahren

Also ist eine Übergabe byValue nur Scheinbar byValue, weil in Wirklichkeit nur die Referenz des Objekts übergeben wird, und im Falle eines returns umgeschrieben wird?

ja und nein. bei einem string stimmt diese aussage. bei allem anderen nciht. das ist die sonderbehandlung des strings in der CLR

3.971 Beiträge seit 2006
vor 15 Jahren

ja und nein. bei einem string stimmt diese aussage. bei allem anderen nciht. das ist die sonderbehandlung des strings in der CLR

Ja, bei allen Klassen (Referenztypen) die als immutable implementiert wurden (kein öffentlichen Funktionen, die die Instanz ändern, sondern immer eine neue Instanz zurückgeben). Neben String lassen sich bestimmt auch noch weitere Klassen im Framework finden, die als immutable implementiert wurden.

Vllt. hilft dir der Vergleich mit C:ByValue ist in C void ByReference ist in C void*

Das ganze gilt für Referenztypen, bei Werttypen musst du bei der übergabe jeweils ein Stern abziehen (oder halt durch & ersetzen)

Operationen über einem Typ sind nur atomar, wenn der Typ kleiner oder gleich der Größe eines Maschinenwortes ist (int, uint, char,...), aber Operationen über der Referenz eines Typs sind immer atomar?

Jupp.
Das heißt aber nicht, dass du beim threadübergreifenden Lesen auf Synchronisierung verzichten darfst. Wie oben schon geschrieben funktioniert das zwar bei Einkern-Rechner. Bei Mehrkern fällst du damit tierisch auf die Schn**ze

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Aschratt Themenstarter:in
97 Beiträge seit 2007
vor 15 Jahren

Alles Karl,

Danke für die Antworten =)

Gelöschter Account
vor 15 Jahren

wie verhält sich das chaching in mehrkerncpu´s bei variablen die volatile sind?
werden diese auch gecached und andere kerne erhalten dadurch die veralteten werte?

S
248 Beiträge seit 2008
vor 15 Jahren

Also ist eine Übergabe byValue nur Scheinbar byValue, weil in Wirklichkeit nur die Referenz des Objekts übergeben wird

Eine Übergabe "By Value" heisst, dass der Inhalt der Variablen kopiert wird (Value => Inhalt). "By Reference" heisst, dass ein Zeiger auf die lokale Variable übergeben wird.

Beispiel:


string str = "Hallo"; // str ist 4 bytes groß (auf 32 bit System)  und könnte nun zB 0x85AB5038 sein
Test(str);

int i = 5; // i ist 4 Bytes groß und beinhaltet 0x00000005
Test(i);

//

void Test(string s)
{
  // s ist 0x85AB5038
}

void Test(int t)
{
  // i ist 0x00000005
}


Sowohl bei Verweistypen als auch bei Werttypen wird der Wert der Variablen kopiert (was bei Werttypen logischerweise der Wert selber ist bei Referenztypen die Referenz).

Was man nicht vergessen darf ist, dass C# bei der Benutzung von "ref" oder "out" für uns automatisch die aus C/C++ bekannte Dereferenzierung macht und vor dem Programmierer versteckt (was man schön sieht, wenn man sich Code zB im Reflector in IL anschaut).

Aschratt Themenstarter:in
97 Beiträge seit 2007
vor 15 Jahren

Also ist eine Übergabe byValue nur Scheinbar byValue, weil in Wirklichkeit nur die Referenz des Objekts übergeben wird

Eine Übergabe "By Value" heisst, dass der Inhalt der Variablen kopiert wird (Value => Inhalt). "By Reference" heisst, dass ein Zeiger auf die lokale Variable übergeben wird. Richtig, wie wir aber oben geklärt haben verhält sich das bei String nicht so, denn der String an sich ist eine Referenz, also wird die Addresse des Strings -> Value kopiert, jedoch wird sie lediglich zurück kopiert, wenn ein return kommt.

@JAck30lena: Würde mich auch interessieren.

wie verhält sich das chaching in mehrkerncpu´s bei variablen die volatile sind?
werden diese auch gecached und andere kerne erhalten dadurch die veralteten werte? Also ich hab das mal getestet. Anscheinend ist es genau so wie du es gesagt hast. Bei einem Schreibzugriff werden die Variablen aber automatisch synchronisiert. Da das aber von einem anderem Thread aus passiert, kann es vorkommen, dass die Werte der flüchtigen Variablen im zweiten Thread veraltet sind.