Jump to content
Unity Insider Forum

Werte in Funktion durch Multithreading verändert


Kokujou

Recommended Posts

Hi!

 

Ich hab ein großes Problem: Ich hab eine Funktion mit Multithreading realisiert und gebe die Werte darin aus.

Aber diese variieren jedes mal wenn ichd as Programm starte. Ich habe schon versucht etwas Fehlerbehebung zu machen, es ist mir völlig unerklärlich was da passiert:

 

public float RateState(State current)
	    {
		    float result = 0f, matValue = 0f, guarding = 0f, threatening = 0f;
		    List<PFigur>[] FigurSets = new List<PFigur>[] { new List<PFigur>(), new List<PFigur>() };
		    FigurSets[0].AddRange(current.wFiguren);
		    FigurSets[1].AddRange(current.sFiguren);
		    float[] FigurGuards = new float[FigurSets[Player.playerID].Count];
		    float[] FigurThreats = new float[FigurSets[1 - Player.playerID].Count];
		    for (int i = 0; i < FigurSets[Player.playerID].Count; i++)
		    {
			    PFigur Figur = FigurSets[Player.playerID][i];
			    matValue += Figur.Punkte;
			    FigurGuards[i] += FigurAsTarget(Figur, FigurSets[Player.playerID]);
			    FigurGuards[i] -= FigurAsTarget(Figur, FigurSets[1 - Player.playerID]);
		    }
		    for (int i = 0; i < FigurSets[1 - Player.playerID].Count; i++)
		    {
			    PFigur Figur = FigurSets[1 - Player.playerID][i];
			    matValue -= Figur.Punkte;
			    FigurThreats[i] += FigurAsTarget(Figur, FigurSets[1 - Player.playerID]);
			    FigurThreats[i] -= FigurAsTarget(Figur, FigurSets[Player.playerID]);
		    }
		    guarding = FigurGuards.Join(FigurSets[Player.playerID], x => x, y => y.Punkte, (x, y) => ((x < -1) ? -1 : ((x > 1) ? 1 : x)) * y.Punkte).Sum();
		    threatening = FigurThreats.Join(FigurSets[1 - Player.playerID], x => x, y => y.Punkte, (x, y) => ((x < -1) ? -1 : ((x > 1) ? 1 : x)) * y.Punkte).Sum();
		    result = matValue + (guarding - threatening);
		    current.Value = result;
		    return result;
	    }

 

dazu die FigurAsTarget Funktion:

 

public int FigurAsTarget(PFigur Figur, List<PFigur> FigurSet)
	    {
		    int count = 0;
		    for (int i = 0; i < FigurSet.Count; i++)
		    {
			    if (FigurSet[0].Color == Figur.Color)
			    {
				    if (FigurSet[i].Movement[Figur.position.y, Figur.position.x] == 5)
					    count++;
			    }
			    else
			    {
				    if (Math.Abs(FigurSet[i].Movement[Figur.position.y, Figur.position.x] - 2.5f) == 0.5f)
					    count++;
			    }
		    }
		    return count;
	    }

 

Das Movement-Array wird außerhalb des Multithreadings berechnet. Ich habe es mehrmals getestet, wenn ich das Multithreading weglasse sind die werte konstant. Bitte helft mir!

 

Ich glaube nicht dass es relevant ist aber ich verwende Tasks a la:

 

tasks.Add(Task.Run(() => { return RateState(state); }));

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn du beim Multithreading seltsame Nebeneffekte hast, liegt das meist daran, dass sich die Threads gegenseitig beeinflussen. Das heißt, wenn ein Thread die gleichen Werte ändert wie der andere, ist es dem Zufall überlassen, welcher schneller ist, und das Ergebnis bekommst du dann eben raus.

 

Du könntest mit lock etwas Ordnung rein bringen, aber wenn du die ganze Methode lockst, hast du effektiv kein Multithreading mehr. Du könntest aber mal kleinere Anweisungsblöcke mit lock umgeben und schauen, ob das Ergebnis besser wird. Ich tippe auf die letzten Zeilen, wo du das Result setzt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

das ist das was mich am meisten wundert... ich habe den gesamten funktionsinhalt in einen lock gesetzt, das gleiche problem. es ändert sich nichts! nur wenn ich das multithreading komplett rausnehme ist der fehler weg. und das ist komisch.

 

ich dachte zuerst dass die funktion schuld ist, in der ich die Movement Arrays ausrechnen lasse weil die ja die figuren verändert, aber im code werden ja eigentlich nur variablen verändert die im Task erzeugt worden! da kann sich ja nichts gegenseitig beeinflussen der Task ist ja praktisch statisch.

Link zu diesem Kommentar
Auf anderen Seiten teilen

wie zeigt man denn die Task-Aufrufe?

for (int i = 0; i < states.Count; i++)
				    {
					    State state = new State(states[i].Zug, states[i].Parent, states[i].playerID, states[i].Value, states[i].Feld, states[i].wFiguren, states[i].sFiguren);
					    for (int j = 0; j < state.wFiguren.Count; j++)
						    Spieler.OnSelect(state.wFiguren, j, state.Feld);
					    for (int j = 0; j < state.sFiguren.Count; j++)
						    Spieler.OnSelect(state.sFiguren, j, state.Feld);
					    tasks.Add(Task.Run(() => { return RateState(state); }));
				    }

so sieht die for-schleife aus in der ich die Ratestate-Funktion aufrufe. Ich übergebe extra Kopien und nicht die Referenz

Link zu diesem Kommentar
Auf anderen Seiten teilen

Deine Zeile

 

State state = new State(states[i].Zug, states[i].Parent, states[i].playerID, states[i].Value, states[i].Feld, states[i].wFiguren, states[i].sFiguren);

erzeugt zwar einen neuen State, aber wie das im Konstruktor umgesetzt wird, sehe ich nicht. Wenn da so was wie

public State(Zug zug, Parent parent, ...)
{
   this.Zug = zug;
   this.Parent = parent;
   ....
}

steht, erzeugst du keine Kopien, sondern arbeitest auf der Original-Instanz von z.B. Zug und Parent.

Link zu diesem Kommentar
Auf anderen Seiten teilen

dann verrat mir mal wie ich eine Klasse am besten Kopiere? Denn ja sowas steht da.

Wie machich es?

Gibt es irgendeine HardCopy Funktion? Bei nem Array benutze ich CopyTo aber das gibts ja leider nicht für Klassen.

 

Das ist oft ein Problem bei mir und ich will nicht jedes mal den Code selbst schreiben müssen wenn ich verschiedene Klassen kopiere und Klassen in Klassen in Klassen ...

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das, was du brauchst, nennt man Deep Copy.

 

Der Hintergrund:

Datentypen in C# teilen sich in zwei Kategorien: Value- und Referenz-Typen. Value-Typen haben einfache Datentypen wie int, string, float usw. Wenn du eine Funktion aufrufst

 

int a = 2;
int b = 3;
int result = myClass.Add(a, ;

dann wird an MyClass.Add der Wert von a und b übergeben. In dem Fall also 2 und 3, der Aufruf wäre identisch zu

int result = myClass.Add(2, 3);

Auch alle struct-Typen sind Value-Typen.

 

Im Gegensatz dazu stehen die Klassen. Wenn du eine Klasse an eine Methode übergibst, dann übergibst du eine Referenz auf diese Klasse. In der Methode arbeitest du also nicht mit einem extra für diese Methode erzeugten neuen Objekt, sondern mit dem Original-Objekt.

 

Beispiel:

class MyRefClass {
   int a;
   int b;
   MyRefClass(int newA, int newB)
   {
    a = newA;
    b = newB;
   }
}
MyRefClass test = new MyRefClass(2, 3);
MyWorkingClass.DoSomethingWith(test);

In der letzten Zeile wird die Methode MyWorkingClass.DoSomethingWith aufgerufen und ihr wird die Referenz auf das Objekt test übergeben. Wenn in der Methode DoSomethingWith z.B. folgender Befehl steht:

test.a = 8;
test.b = 0;

dann sind in der originalen Klasse test die Werte verändert, nicht nur in der Methode.

 

Die Eigenschaften von Referenz-Objekten sind z.B., dass unabhängig von der Menge an Daten in der Klasse immer nur ein "Zeiger" auf das Objekt übergeben wird und dass die Daten dieser Objekte dann entsprechend leicht manipuliert werden können.

 

Du musst auch aufpassen, wenn du einen struct-Typ einfach in eine class umwandelst. Das hat gravierende Nebeneffekte, die nicht so ohne weiteres ersichtlich sind. Nämlich überall da, wo diese Klasse als Parameter verwendet wurde.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Keine Sorge, ich hab das mit den Referenzen schon verstanden. Dann werde ich mal sehen wie ich eine DeepCopy Funktion implementiere. Das hab ich jetzt mal versucht aber ich krieg eine Ausnahme, von wegen meine Klasse ist nicht serialisierbar.

 

OK offenbar musste man sie nur als Serialisierbar kennzeichnen... Gut zu wissen, dachte nicht es wär so einfach.

Allerdings wird die Laufzeit jetzt stark verringert. Kann das sein?

 

Ich hab das mal getestet und offenbar ist das kopieren so langsam dass es besser wäre das Multithreading komplett wegzulassen.

Natürlich wollte ich das Klonen auch in den Thread packen, aber leider Gottes gibt es dann eine Verletzung der Liste.

 

Wenn ich dies hier in die Funktion die in den Tasks gerechnet wird schreibe:

State state = Clone<State>(states[i]);
			    for (int j = 0; j < state.wFiguren.Count; j++)
				    Spieler.OnSelect(state.wFiguren, j, state.Feld);
			    for (int j = 0; j < state.sFiguren.Count; j++)
				    Spieler.OnSelect(state.sFiguren, j, state.Feld);

 

Kommt eine IndexOutOfRangeException

Link zu diesem Kommentar
Auf anderen Seiten teilen

Diese Zeile löst den Fehler aus

tasks.Add(Task.Run(() => { return RateState(state); }));

 

ich bin nicht sicher aber ich nehme an es liegt daran dass ich versuche ein Element der Liste zu verändern und das in Threads. Egal ob ich die komplette Liste übergebe, dann einen Klon anfertige und mit diesem weiterarbeite,

oder ob ich das Listenelement übergebe und über die Referenz klone, statt über den direkten index, was sich ja eigentlich nicht viel nimmt.

 

Die Clone Funktion hab ich übernommen.

public static T Clone<T>(T source)
	{
		if (!typeof(T).IsSerializable)
		{
			throw new ArgumentException("The type must be serializable.", "source");
		}
		// Don't serialize a null object, simply return the default for that object
		if (Object.ReferenceEquals(source, null))
		{
			return default(T);
		}
		IFormatter formatter = new BinaryFormatter();
		Stream stream = new MemoryStream();
		using (stream)
		{
			formatter.Serialize(stream, source);
			stream.Seek(0, SeekOrigin.Begin);
			return (T)formatter.Deserialize(stream);
		}
	}

 

PS: Ich hab mal in der Funktion etwas ausgeklammert und die Serialize-Funktion frisst am meisten, danach direkt die Deserialize. Die Laufzeit wird dadurch verzehnfacht, wenn das mal reicht.

 

PPS: mir ist eine vielleicht wichtige Information eingefallen. die State-Klasse beinhaltet praktisch den gesamten Zustandsbaum. Nachfolger- und Vorgängerzustände. das könnte vielleicht die Dauer der Serialize-Funktion erklären. Aber aktuell wird der Baum nur bis in Suchtiefe 2 aufgebaut.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wenn du ziemlich viel kopieren musst ist das schon recht normal dass das was länger dauern kann.

 

Die Frage ist also, willst du wirklich kopieren oder kopierst du vielleicht sogar zu viel was überflüssig ist ?

Allgemein auch, bist du sicher dass du überhaupt multithreading brauchst ?

Du merkst ja schon, das Thema ist nicht so einfach :)

Multithreading bringt auch bloß performance Vorteile, wenn genug Arbeit zu tun ist.

Link zu diesem Kommentar
Auf anderen Seiten teilen

ich baue Zustandsbäume für ein Schachspiel auf also ja, multithreading ist definitiv nötig und funktioniert auch sehr gut. ich komme auf 100% CPU auslastung.

Ich entwickle ein Schachspiel, ihr merkt es wenn ihr meine früheren Threads verrfolgt

aber ich schätze ich muss es erstmal ausklammern und etwas basteln. auf jeden fall danke! die frage ist damit ja gelöst nur nicht wie ich die lösung auch schnell anwende XD

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...