myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE
   » Gadget für Windows
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
» Accessoires

Ressourcen
» .NET-Glossar
» guide to C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» Search.Net

Team
» Kontakt
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » FAQ » [FAQ] Random.Next liefert eine Zeit lang die gleiche Zufallszahl - Warum? Wie geht es richtig?
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | An Freund senden | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[FAQ] Random.Next liefert eine Zeit lang die gleiche Zufallszahl - Warum? Wie geht es richtig?

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
xxMUROxx xxMUROxx ist männlich
myCSharp.de-Poweruser/ Experte

images/avatars/avatar-3236.jpg


Dabei seit: 11.01.2010
Beiträge: 1.552
Entwicklungsumgebung: VS 13, SSMS 12
Herkunft: Südtirol/Italien


xxMUROxx ist offline

[FAQ] Random.Next liefert eine Zeit lang die gleiche Zufallszahl - Warum? Wie geht es richtig?

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Community,

Das Generieren einer zufälligen Zahlenfolge wird oft benötigt; dazu bietet sich die  Random-Klasse an. Jedoch wird leider mit dieser Klasse sehr oft falsch umgegangen.

Folgende Fragen gibt es in diesem Forum zur Genüge:
  • Warum werden immer dieselben zufälligen Zahlen generiert?
  • Random funktioniert nicht wie erwartet.
Die Klasse selbst ist korrekt, die Ursache für die Probleme liegt in der Benutzung.


1. Gleicher Seed - Gleiche Zufallsreihenfolge

Nehmen wir mal die erste Überladung der Konstruktors unter die Lupe: diese nimmt einen Seed, welcher für die Berechnung der Zufallszahlen essentiell ist, als Parameter.

Den Seed an sich kann man als eine Art Startwert für die Generierung der Zufallszahlen sehen. Die Tatsache ist so, dass es keinen perfekten Zufallszahlenalgorithmus gibt und somit die anfänglich übermittelte Zahl zur Berechnung der Zufallszahlen hergenommen wird.

Ist dieser Seed bei wiederholtem Abrufen der Zahlenfolge gleich, so wird auch die gleiche Zahlenfolge generiert. So auch bei folgendem Code:

C#-Code (Ein konstanter Seed ist nur in Ausnahmefällen sinnvoll):
Random r1 = new Random(1);
for (int i = 0; i < 5; ++i)
{
    Console.Write(r1.Next(0, 6)+ " ");
}
//Der dazugehörige output ist und bleibt: 1 0 2 4 3

Jedoch kann es beim Debuggen hilfreich sein, immer den selben Seed zu verwenden, da so immer die gleiche Folge generiert wird.

Im folgenden sprechen wir jedoch nur noch über Random-Objekte, die mit dem parameterlosen Konstruktor erstellt werden.


2. Zeitabhängiger Seed

Um die vorhergehende Tatsache zu unterbinden, muss man einen möglichst zufälligen Seed wählen. Da Standard-PCs nicht über echte Zufallsgeneratoren verfügen, weicht man üblicherweise auf die aktuelle (Uhr-)Zeit oder Teile davon aus.

Der Standardkonstruktor von Random verwendet die Anzahl der verstrichenen Millisekunden seit Systemstart ( Environment.TickCount) als Seed. Das ist in den allermeisten Fällen ausreichend gut, sofern man die folgende Falle umgeht.


3. Falle: Mehrfaches Erzeugen von Random-Objekten

Wenn eine Folge an Zufallszahlen benötigt wird, werden diese gern in einer Schleife erstellt. Was aber beim falschen Umgang zu einen Fehler führt.

C#-Code (FALSCH):
for (int i = 0; i < 5; ++i)
{
    Random rnd = new Random();
    Console.Write("{0} ", rnd.Next());
}
// Gibt z.B. folgende Ausgabe: 4 4 4 4 4

Was ist an dem Code falsch?
Dies beschreibt die MSDN Dokumentation wie folgt:

Zitat von Random-Konstruktor (System):
Der Standard-Startwert wird von der Systemuhr abgeleitet und verfügt über eine endliche Auflösung. Unterschiedliche Random-Objekte, die unmittelbar nacheinander durch einen Aufruf des Standardkonstruktors erstellt werden, verfügen daher über identische Standard-Startwerte und erzeugen so identische Sätze von Zufallszahlen.

Und da die PCs so schnell laufen, werden neue Random-Objekte in der Schleife unmittelbar nacheinander erstellt und somit ist auch die Systemzeit in sehr vielen Schleifendurchläufen dieselbe und somit auch der Seed, so dass das unter Punkt 1 erwähnte seine Auswirkung zeigt.

3.1. Warum funktionierts im Debugger?

Wenn man hingegen im Debugger Zeile für Zeile durchgeht, dann erhöht sich damit die Zeit zwischen den Schleifendurchläufen drastisch, wodurch die Random-Instanzen mit unterschiedlichen Zeiten initialisiert werden.

3.2. Lösung des Problems

Um dieses Problem zu umgehen, reicht es bereits, die Random-Instanz außerhalb der Schleife - oder noch besser nur ein mal pro Objekt (Instanzvariable) oder nochmal besser nur einmal pro Klasse (statische Variable) - zu erzeugen:

C#-Code (Nicht schlecht, aber nicht perfekt):
private void GenerateRandom()
{
    Random rnd = new Random();
    for (int i = 0; i < 5; ++i)
    {
        Console.Write("{0} ", rnd.Next());
    }
}

3.2.1 Warum ist eine Instanzvariable oder statische Variable besser

Vorhergehender Code sichert ab, dass das Random-Objekt nicht in jedem Schleifendurchlauf neu erstellt wird. Jedoch kann es auch vorkommen, dass die Methode GenerateRandom() selbst in einer Schleife ausgeführt wird. Dadurch kann es passieren, dass jedes neue Random-Objekt wiederum den identischen Seed bekommt und wir wieder das Problem von Punkt 3 haben. Folgendes Beispiel soll dies verdeutlichen:

C#-Code (FALSCH):
private void Test()
{
    int i = 0
    while(i++ < 5)
    {
        GenerateRandom();
    }
}

private void GenerateRandom()
{
    Random rnd = new Random();
    for (int i = 0; i < 5; ++i)
    {
        Console.Write("{0} ", rnd.Next());
    }
}

Hiermit haben wir exakt dasselbe Problem wie in Punkt 3, dass ein neues Random-Objekt in jedem Schleifendurchgang der while-Schleife erzeugt wird.

Durch die Deklaration und Initialisierung von rnd als Instanzvariable wird erreicht, dass das Random-Objekt zumindest im beinhaltenden Objekt nur ein Mal erstellt wird.

Das reicht aber wieder nur dann, wenn sichergestellt ist, dass die Objekte der Klasse nie in einer Schleife oder auf andere Weise schnell hintereinander erstellt werden. Deshalb ist es normalerweise noch besser, das Random-Objekt nur einmal pro Klasse zu erstellen.

C#-Code (RICHTIG):
private static Random rnd = new Random();
private void GenerateRandom()
{
    for (int i = 0; i < 5; ++i)
    {
        Console.Write("{0} ", rnd.Next());
    }
}

Fazit: Man muss auf jeden Fall verhindern, dass mehrere Random-Objekte mit dem parameterlosen Konstruktor schnell hintereinander erstellt werden.


4. Threadsicheres Erstellen von Zufallszahlen

Die Random-Klasse ist nicht threadsicher, daher darf nicht mit mehreren Threads gleichzeitig auf eine Instanz von Random zugegriffen werden. Ein naiver Ansatz ist die Synchronisation des Zugriffs auf die Instanz, z.B. durch die Verwendung eines locks:

C#-Code (Nicht schlecht, aber nicht perfekt):
private static Random _rnd = new Random();

public int GetThreadSafeRandomNumber()
{
    lock (_rnd)
    {
        return _rnd.Next();
    }
}

Dieser Ansatz ist einfach und auch richtig, allerdings wird bei jedem Zugriff synchronisiert und das ist aus Performance-Sicht suboptimal. Je mehr Threads so Zufallszahlen erzeugen wollen, desto höher die "Resource Contention". Um dies zu vermeiden kann eine Random-Instanz pro Thread verwendet werden (anstatt einer pro Anwendungsdomäne, wie im naiven Ansatz), deren Seed z.B. durch eine anwendungsdomänenweite Random-Instanz generiert wird.

C#-Code (RICHTIG):
[ThreadStatic]
private static Random _rnd;

private static int _seed = Environment.TickCount;
public int GetThreadSafeRandomNumber()
{
    if (_rnd == null)
    {
        int seed = Interlocked.Increment(ref _seed);
        _rnd     = new Random(seed);
    }
    return _rnd.Next();
}

5. Siehe auchGruß,
Michael
Neuer Beitrag 21.10.2011 10:28 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 3 Jahre.
Der letzte Beitrag ist älter als 3 Jahre.
Antwort erstellen


© Copyright 2003-2014 myCSharp.de-Team. Alle Rechte vorbehalten. 22.11.2014 10:59