Bewertung: 4.9 von 7 Benutzern
klaus_b
Wie im vorherigen Artikel angesprochen, will ich dieses mal die Klasse ClientList, welche die Ergebnisse des Scans speichert, vorstellen. Zunächst galt es ein Basiselement festzulegen welches mehre Datenreihen, jede Reihe bestehend aus verschiedenen Datentypen, speichert und dabei noch leicht zu handhaben ist. Von eigenen Konstrukten bin ich schnell abgerückt und habe mich für die DataTable entschieden. Sie besitzt alle notwendigen Eigenschaften die ich benötige:
- sie ist serialisierbar, zur Verwendung mit anderen Prozessen
- den Export der Ergebnisse im XML-Format gibt es gratis dazu
- sie besitzt mit dem DataTableReader einen schnellen und einfachen Vorwärts-Cursor
- das jeweilige Ergebnis kann, in Form einer DataRow, an der Position des Cursors eingefügt oder ersetzt werden.
Einziger Wermutstropfen ist das Einfügen einer DataRow in Multithread-Anwendungen. Zitat MSDN:
Dieser Typ ist bei Multithread-Lesevorgängen sicher. Sie müssen alle Schreibvorgänge synchronisieren.
Doch dazu später mehr.
Als erstes werden die Namen und der Datentyp der jeweiligen Spalte festgelegt. Benötigt wird hier: die IP-Adresse, der Hostnamen und der Online-Status eines jeden LAN-Client. Der Einfachheit halber habe ich mich entschieden die IP-Adresse als Zeichenfolge zu speichern, der Hostnamen ist sowieso eine Zeichenfolge und der Online-Status wird als boolscher Wert abgelegt. Da ich auf die DataTable in der ganzen Klasse zugreifen will, wird sie als Class Member instanziiert um im Konstruktor initialisiert und auch gleich das Schema festgelegt:
public ClientList()
{
// Mutex initialisieren
this.mutex = new Mutex(false);
// neue DataTable erzeugen und das Schema festlegen
this.table = new DataTable("Clients");
this.table.Locale = CultureInfo.CurrentCulture;
this.table.Columns.Add("IpAddress", typeof(string));
this.table.Columns.Add("Name", typeof(string));
this.table.Columns.Add("Online", typeof(bool));
}
So ist gewährleistet, dass bei der Instanziierung der Klasse die DataTable immer richtig erzeugt wird.
Da ich aber nicht nur Daten anfügen will sondern sich der Online-Status eines Clients bei einem wiederholten Scan geändert haben kann, muss es auch die Möglichkeit geben Daten zu ersetzen. Leider bietet die DataTable keine Möglichkeit dazu. Doch mit einem kleine Kniff ist auch das zu lösen. Die DataTable bietet über die DataRowCollection die Methoden RemoveAt(int index) und InsertAt(DataRow row, int pos). Nacheinander ausgeführt hat man auch eine Update-Methode.
// da keine Update-Methode zur Verfügung steht,
// Reihe erst löschen und am selben Index einfügen
this.table.Rows.RemoveAt(i);
this.table.Rows.InsertAt(row, i);
Nachdem die Handhabung der Daten in der DataTable geklärt ist, steht noch die Threadsicherheit aus. Als Mechanismus zur Synchronisierung habe ich nicht die lock-Anweisung gewählt, sondern mich für die Verwendung eines Mutex entschieden. Aus meiner Sicht ist der Mutex ein sehr flexibles Werkzeug und lässt sich auch in einem Fehlerfall noch sehr gut handhaben. Ein zweiter wichtiger Punkt ist die Möglichkeit, für eine spätere Erweiterung der Klasse, den Mutex als systemweiten Mutex in einer Eigenschaft nach außen verfügbar zu machen und als WaitHandle zu nutzen. Der entscheidende Punkt für mich war jedoch die Möglichkeit bei einem Threadfehler, z.B. wenn ein Thread in der Bearbeitung abgebrochen wird der gerade den Mutex besitzt, diesen Fehler abzufangen und den Mutex noch freigeben zu können. Nach dem ein Thread beendet oder abgebrochen wurde der gerade im Besitz des Mutex war, geht der Mutex auf den nächsten Thread in der Warteschlange über. In diesem nächsten Thread wird eine AbandonedMutexException ausgelöst, auf die reagiert werden kann. Ich verwende in der ClientList Klasse die Eigenschaft AddedSuccessfull die der aufrufenden Methode signalisiert ob das Ergebnis in die DataTable geschrieben werden konnte. Diese Eigenschaft gibt die private Member Variable addedSuccessfull zurück. Diese private Variable wird mit dem Schlüsselwort volatile instanziiert um zu gewährleisten, dass auch wirklich der letzte zugewiesene Wert der Variablen zurückgegeben wird. Im try-catch Block wird nun im catch diese Variable auf false gesetzt und somit der aufrufenden Methode signalisiert, dass die Daten nicht geschrieben werden konnten. Hier ein Beispiel:
try
{
// aktuellen Thread blockieren um
// sicher die Daten in der DataTable zu bearbeiten
this.mutex.WaitOne();
// weiterer Code
}
catch (AbandonedMutexException)
{
this.addedSuccessfull = false;
}
finally
{
// Mutex auch im Fehlerfall freigeben
this.mutex.ReleaseMutex();
}
Somit kann sichergestellt werden, dass die Ergebnisse auch wirklich sauber geschrieben werden. Zumindest solange, bis jemand den Gegenbeweis antritt. Für interessierte füge ich hier noch als letztes Listing die komplette Klasse an. Die XML-Kommentare habe ich noch nicht erstellt, aber davon abgesehen sollte die Klasse komplett sein.
internal class ClientList : IDisposable
{
#region Fields
/// <summary>
///
/// </summary>
/// <remarks></remarks>
private volatile bool addedSuccessfull;
/// <summary>
///
/// </summary>
/// <remarks></remarks>
private bool disposed;
/// <summary>
///
/// </summary>
/// <remarks></remarks>
private Mutex mutex;
/// <summary>
///
/// </summary>
/// <remarks></remarks>
private DataTable table;
#endregion Fields
#region Constructors
/// <summary>
///
/// </summary>
/// <remarks></remarks>
public ClientList()
{
// Mutex initialisieren
this.mutex = new Mutex(false);
// neue DataTable erzeugen und das Schema festlegen
this.table = new DataTable("Clients");
this.table.Locale = CultureInfo.CurrentCulture;
this.table.Columns.Add("IpAddress", typeof(string));
this.table.Columns.Add("Name", typeof(string));
this.table.Columns.Add("Online", typeof(bool));
}
#endregion Constructors
#region Public Properties
/// <summary>
///
/// </summary>
/// <value></value>
/// <remarks></remarks>
public bool AddedSuccessfull
{
get { return this.addedSuccessfull; }
}
/// <summary>
///
/// </summary>
/// <value></value>
/// <remarks></remarks>
public DataTable ClientTable
{
get
{
return this.table;
}
}
#endregion Public Properties
#region Private Methods
/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
/// <remarks></remarks>
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.table.Dispose();
this.mutex.Close();
}
}
this.disposed = true;
}
#endregion Private Methods
#region Public Methods
/// <summary>
///
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="name"></param>
/// <param name="online"></param>
/// <exception cref="T:System.ArgumentException">
///
/// </exception>
/// <exception cref="T:System.ObjectDisposedException">
///
/// </exception>
/// <remarks>n/a</remarks>
public void AddOrReplace(string ipAddress, string name, bool online)
{
// prüfen ob Dispose bereits ausgeführt wurde
if (this.disposed)
{
throw new ObjectDisposedException("ClientList");
}
this.addedSuccessfull = false;
// ip-String auf Gültigkeit prüfen
if (!IPHelper.ValidateIP(ipAddress))
{
throw new ArgumentException("Die übergebene Zeichenfolge ist keine gültige IP-Adresse.");
}
try
{
// aktuellen Thread blockieren um
// sicher die Daten in der DataTable zu bearbeiten
this.mutex.WaitOne();
// neue DataRow zum Anfügen oder Ersetzen initialisieren
DataRow row = table.NewRow();
row[0] = ipAddress;
row[1] = name;
row[2] = online;
// Index initialisieren
int i = 0;
// prüfe ob die DataTable bereits Elemente enhtällt
if (this.table.Rows.Count > 0)
{
// neuen DataTableReaser mit der lokalen DataTable initialisieren
DataTableReader reader = new DataTableReader(this.table);
// durch die DataTable itterieren
while (reader.Read())
{
// IP-Adressen vergleichen
if (string.Equals(reader.GetString(0), ipAddress, StringComparison.Ordinal))
{
// IP-Adresse ist vorhanden
// prüfe ob Änderungen in der Reihe
if (!string.Equals(reader.GetString(1), name, StringComparison.OrdinalIgnoreCase)
|| reader.GetBoolean(2) != online)
{
// da keine Update-Methode zur Verfügung steht,
// Reihe erst löschen und am selben Index einfügen
this.table.Rows.RemoveAt(i);
this.table.Rows.InsertAt(row, i);
// signalisieren, dass die DataTable erfolgreich bearbeitet wurde.
this.addedSuccessfull = true;
// Reihe ersetzt, beenden
return;
}
else
{
// signalisieren, dass die DataTable erfolgreich bearbeitet wurde.
this.addedSuccessfull = true;
// keine Änderung, beenden
return;
}
}
// Index erhöhen
i++;
}
}
// DataRow anhängen
this.table.Rows.Add(row);
// signalisieren, dass die DataTable erfolgreich bearbeitet wurde.
this.addedSuccessfull = true;
}
catch (AbandonedMutexException)
{
this.addedSuccessfull = false;
}
finally
{
// Mutex auch im Fehlerfall freigeben
this.mutex.ReleaseMutex();
}
}
/// <summary>
///
/// </summary>
/// <remarks>n/a</remarks>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
#endregion Public Methods
}
Parallel Ping Tests
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitten "kicken" sie ihn.
