Java hat es vorgemacht: Ein einfaches Konzept namens Webstart ermöglicht eine einfache Verteilung einer Anwendung über das Web. Dabei wird die Applikation über einen Browser aufgerufen, welcher die benötigten Downloads automatisch tätigt und die Applikation startet. Diese Art der Softwareverteilung hat gewisse Vorteile wie beispielsweise die Zugreifbarkeit oder die Updatemöglichkeit ohne, dass der Benutzer aktiv werden muss.
Die Antwort von .NET heisst ClickOnce. Das Prinzip dahinter ist grundsätzlich genau gleich. So können Applikationen direkt auf einem Webserver veröffentlicht werden. Die Veröffentlichung von ClickOnce-Applikationen ist dabei spielend einfach. Im Eigenschaften Fenster des Projekts müssten im Reiter “Publish” einfach der Exportpfad sowie der entgültige Pfad auf dem Webserver eingegeben werden. Der Entwickler hat dier noch mehrere Einstellmöglichkeiten welche sich hinter dem “Options” Button verbergen. Wurde der gesamte Export auf den Webserver geladen, kann die Applikation auf 2 Arten gestartet werden:
- Mit einem direkten Start über die “.application” Datei (funktioniert mit dem IE Einwandfrei, während Firefox dazu evtl. das Addon Microsoft .NET Framework Assistant benötigt)
- Mit einem vorherigen Download eines Setups, welches prüft ob die vorausgesetzten Software Packages installiert sind (Framework, Windows Installer, etc.).
Als kleines Beispiel habe ich eine Hello World-Applikation gemacht, welche ganz einfach über einen Browser aufrufbar ist.
Starte die ClickOnce-Demo (leitet dich auf eine Seite weiter, welche von Visual Studio generiert wurde und beide obengenannten Startmöglichkeiten verlinkt hat)
Standardmässig lässt C# es nicht zu, dass das GUI von einem anderen Thread aktualisiert wird, als von jenem der es erstellt hat. C# wirf eine InvalidOperationException mit dem hinweis, dass versucht wurde das GUI von einem Thread zu aktualisieren, welche nicht der Besitzer ist. Es gibt jedoch möglichkeiten wie man trotzdem von anderen Threads auf das GUI schreiben kann. Dazu dient beispielsweise die Eigenschaft
Control.CheckForIllegalCrossThreadCalls
Diese ist in C# von Beginn weg auf True gestellt. Es gibt nun zwei verschiedene Möglichkeiten wie man von anderen Threads trotzdem die GUI Komponenten aufrufen kann:
- Man stellt die obengenannte Eigenschaft auf false, womit keine Prüfung mehr erfolgt ob es ein threadübergreifender Vorgang ist
- Man benutzt die InvokeRequired-Methode welche prüft ob ein threadübergreifender Vorgang vorliegt und Methode nochmals vom GUI-Thread aufgerufen werden soll.
Da die erste Möglichkeit mit einer Codezeile erledigt wird, möchten wir uns die zweite Methode genauer anschauen. Dabei kommen Delegates zum Einsatz. Wie der Name schon sagt werden dabei Methodenaufrufe delegiert. Der Methodenaufruf wird dabei an den den GUI-Thread übergeben, welche die Methode von sich aus aufruft. Damit haben wir das Problem des threadübergreifenden Zugriffs elegant gelöst!
Als kleines Beispiel habe ich ein Projekt erstellt welche einerseits mit der BeginInvoke-Methode arbeitet und andererseits keine Prüfung beinhaltet (also eine Exception wirft). Um die Abläufe der Aufrufe am besten zu verfolgen wird empfohlen das Projekt im Debugging-Modus auszuführen. So ist klar ersichtlich welche Aufrufe wann passieren und es ist leichter nachvollziehbar. Der zentrale Code mit den Delegates lautet folgendermassen:
delegate void ChangeLabelSizeCallback(float newSize);
private void ChangeLabelSize(float newSize)
{
if (lblTarget.InvokeRequired)
{
ChangeLabelSizeCallback lsc = new ChangeLabelSizeCallback(ChangeLabelSize);
lblTarget.Invoke(lsc, new Object[] { newSize });
}
else
{
lblTarget.Font = new Font(lblTarget.Font.FontFamily, newSize);
lblTarget.Text = "FontSize: " + newSize.ToString();
}
}
Anbei steht das gesamte Beispiel noch zum Download bereit:

Während der Softwareentwicklung hat man sehr viel mit Events zu tun. Meistens werden diese von bestimmten Klassen zur Verfügung gestellt. Die gängisten Events sind sicherlich dienenigen der GUI Komponenten. Häufig liefern die Events Zusatzinformationen in Form vom Parametern, welche man danach auswerten kann. Doch was ist, wenn man eigene Events programmieren und diesen eigene Parameter übergeben möchte?
In C# ist dies wirklich sehr einfach gelöst. Es benötigt nur ein paar Zeilen Code und schon hat man einen eigenen Event erstellt, der von überallher angezapft werden kann. Als kleines Beispiel habe ich ein Konsolenanwendung geschrieben, welche eine boolsche Variable hat, welche regelmässig ihren Wert ändert. Da man aber zum Vornherein nicht genau weis, wann das sein wird, wird ein Event zur Verfügung gestellt welche alle horchenden Objekte benachrichtigt. Bei einer erhaltenen Benachrichtigung wird dies auf dem Konsolenfenster ausgegeben.

Dies ist die Klasse die den Event zur Verfügung stellt und ihn nötigenfalls abfeuert:
using System;
using System.Timers;
namespace EventExample
{
public delegate void FlagValueChangedEventHandler(bool newValue);
public class EventSource
{
public event FlagValueChangedEventHandler FlagValueChanged;
private bool m_Flag;
private Timer m_Timer = new Timer();
private Random m_Random = new Random();
/// <summary>
/// Konstruktor
/// </summary>
public EventSource()
{
//Event für Timer wird angezapt
m_Timer.Elapsed += new ElapsedEventHandler(m_Timer_Elapsed);
}
/// <summary>
/// Berechnet den neuen Interval und starten den Timer
/// </summary>
public void Start()
{
//Zufälliges Intervall zwischen 1 und 10 Sekunden wird ermittelt
m_Timer.Interval = (m_Random.Next(9) + 1) * 1000;
//Timer wird gestartet
m_Timer.Start();
}
/// <summary>
/// Ereignis welches auftritt wenn die Intervalzeit abgelaufen ist
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void m_Timer_Elapsed(object sender, ElapsedEventArgs e)
{
//Timer wird gestoppt
m_Timer.Stop();
//Wert des boolschen Flags wird geändert
m_Flag = !m_Flag;
//Wenn jemand auf den Event horcht wird dieser abgefeuert
if (FlagValueChanged != null)
{
FlagValueChanged(m_Flag);
}
//Spiel beginnt wieder von neuem
Start();
}
}
}
Multithreading kommt dann zum Zug wenn im Hintergrund mehrere Dinge gleichzeitig geschehen sollen. Dies kann die unterschiedlichsten Gründe haben. Ein bekannter Grund um Multithreading einzusetzen ist sicherlich der Umstand, dass das GUI nicht mehr auf Benutzereingaben reagiert, solange im Hintergrund eine komplexe Berechnung stattfindet. Um nun auf eine einfache Art und Weise zu zeigen wie Multithreading in C# funktioniert, habe ich eine kleine Konsolen-Applikation geschrieben. Diese führt immer den gleichen Code aus, mit dem Unterschied, dass man nach dem Starten der Anwendung wählen kann ob man im Multithreading-Modus weiterfahren möchte oder nicht. Das Programm gibt lediglich Outputs auf das Konsolenfenster. Durch diese Ausgabe ist klar ersichtlich, wann welcher Thread die Rechenzeit zugesprochen erhalten hat. Hat man das Multithreading eingeschaltet, wechseln sich die beiden Threads ab, während im “normalen” Modus alles nacheinander ausgegeben wird.

using System;
using System.Threading;
namespace MultiThreading
{
class Program
{
static void Main(string[] args)
{
ConsoleKeyInfo keyAnswer;
//Solange Fragen bis entweder Y oder N gedrückt wurde
do
{
Console.Clear();
Console.WriteLine("Möchten Sie die Applikation im Multithreading Modus starten? (Y/N)");
keyAnswer = Console.ReadKey(true);
}
while (keyAnswer.Key != ConsoleKey.Y && keyAnswer.Key != ConsoleKey.N);
//Je nach dem was gewählt wurde, die Methoden im entsprechenden Modus ausführen
switch (keyAnswer.Key)
{
case ConsoleKey.Y:
//Threading Modus
Thread t1 = new Thread(new ThreadStart(ThreadOneOutput));
Thread t2 = new Thread(new ThreadStart(ThreadTwoOutput));
t1.Start();
t2.Start();
break;
case ConsoleKey.N:
//Normaler Modus
ThreadOneOutput();
ThreadTwoOutput();
break;
}
//Wartet auf irgendeine Eingabe und beendet das Programm danach
Console.ReadKey(true);
}
/// <summary>
/// Methode welche als erster Thread ausgeführt werden kann
/// </summary>
private static void ThreadOneOutput()
{
for (int i = 1; i <= 50; i++)
{
Console.WriteLine("Thread 1: Output Nr. " + i.ToString());
}
}
/// <summary>
/// Methode welche als zweiter Thread ausgeführt werden kann
/// </summary>
private static void ThreadTwoOutput()
{
for (int i = 1; i <= 50; i++)
{
Console.WriteLine("Thread 2: Output Nr. " + i.ToString());
}
}
}
}