en / de
AI
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

C# Concurrency Teil 5: Cross Thread Aufrufe

In Teil 2 Die Gefahren von Multithreading haben wir gesehen, dass nur der Thread auf ein Steuerelement zugreifen darf, wenn er das Steuerelement auch kreiert hat (UI Thread). Wenn ein anderer Thread das Element versucht zu ändern, gibt es eine Cross Thread Exception.

Dieser Blog setzt sich mit dem Thema auseinander und erklärt verschiedene Lösungen.

Das Problem

Eine WinForm oder WPF Applikation hat normalerweise nur einen UI Thread (Splash Screens können eine Ausnahme sein). Weil das UI schnell reagieren soll, werden langläufige Operationen normalerweise parallel ausgeführt. Oft muss ein nebenläufiger Thread auf das UI zugreifen. Dies darf aber nur der UI Thread, sonst kommt es zu einer ‚Cross-thread operation‘ Ausnahme.

noser blog cross thread exceptions

Um das Problem zu lösen, muss der Aufruf auf den UI Thread umgeleitet werden. Das Umleiten auf den UI Thread wird auch Thread Marshaling, Thread Redirection oder Thread Synchronisation genannt.

Die folgenden Programmarten verlangen eine Thread Synchronisation:

Und eine Console Applikation?
Diese hat keine Windows Message Queue oder Probleme, wie ASP.NET sie kennt. Jeder Thread darf zum Beispiel frei auf die Console zugreifen.

Eine Console Applikation braucht also keine Thread Synchronisierung.

Beim setzen einer Eigenschaft im ViewModel leitet NotifyPropertyChanged den Aufruf auf den UI Thread um. Das gilt aber nicht für Collections; dort muss man selber den Aufruf auf den UI Thread umleiten.

Old Style Lösung

Der bekannte Weg bei WinForms und WPF ist, erst zu prüfen ob der Thread der den UI Code ausführt auch der UI Thread ist. Wenn nicht, wird der Aufruf umgeleitet.

Lösung für WinForms

Bei WinForms prüft man mit InvokeRequired, ob der Aufruf auf dem UI Thread stattfindet. Wenn nicht, dann kann mit BeginInvoke die Aktion auf den UI Thread umgeleitet werden. Hinter den Kulissen wird ein User Item in die Windows Message Queue eingereiht, der UI Thread holt das User Item aus der Queue und führt es aus.

Delegate void DlgtSetValue(string text);

public void SetValue(string text)
{      
    DlgtSetEnabled dlgt = new DlgtSetEnabled(SetValue);
    if (this.InvokeRequired)
    {
         this.BeginInvoke(dlgt, new object[] { text });
    }
    else
    {
         _txtValue = text;
    }
}

Lösung für WPF

Wie WinForms arbeitet WPF auch mit dem Single Threaded Apartment Programmiermodel. Die Main Methode ist geflagged mit dem [STAThread] Attribute (siehe App.g.cs).

STA heisst, dass der Code nur durch einen Thread gleichzeitig ausgeführt werden kann und zwar immer der gleiche Thread: der UI Thread.

Damit die Programmierer mehr Kontrolle darüber haben, wann und wie der UI Thread etwas ausführt, wurde der Dispatcher implementiert. Der Dispatcher ist ein UI Scheduler welcher Berichte entgegennimmt und sie an die zuständigen Objekten via den UI Thread weiterleitet.

Mit dem Dispatcher lässt sich der Aufruf vom Worker Thread auf den UI Thread umleiten.

private void button_Click(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolMethod));
}

private void ThreadPoolMethod(object state)
{
    SetTime(DateTime.Now);
}

private void SetTime(DateTime dateTime)
{
    if (Dispatcher.CheckAccess() == false)
    {
        Dispatcher.BeginInvoke(new Action<DateTime>(SetTime), dateTime);
        return;
    }
    // This code executes always on UI Thread
    textBox.Text = dateTime.ToString();
}

BeginInvoke() führt die Aktion auf dem UI Thread aus, wartet aber nicht bis sie fertig ist.
Invoke() für die Aktion auf dem UI Thread aus und blockiert diesen, bis sie fertig ist.

Control.BeginInvoke() und Control.Invoke() dürfen nicht verwechseln werden mit Delegate.BeginInvoke() und Delegate.Invoke()!

Delegate.BeginInvoke() führt das Delegate auf einem ThreadPool Thread aus, aber wartet nicht bis sie fertig ist, Delegate.Invoke() wartet.
Dispatcher.CurrentDispatcher gibt den Dispatcher vom jetzigen Thread zurück, dies muss nicht unbedingt der UI Thread sein.
Application.Current.Dispatcher gibt immer das Dispatcher Objekt vom UI Thread zurück.

SynchronizationContext

Bei der Einführung von async\await hat man das Problem der Thread Synchronization erkannt und mit dem SynchronizationContext abstrahiert. Der SynchronizationContext ist eine Abstraktionsschicht (Facade) für das Thread Marshalling.

Das hat folgende Vorteile:

Jede Programmart hat seine eigene Implementation:

Zum Benutzen muss man sich auf dem UI Thread den SynchronizationContext mit SynchronizationContext.Current speichern.

Von einem anderen Thread kann man mit SynchronizationContext.Post() asynchrone Aufrufe auf den UI Thread umleiten. Synchrone Aufrufe werden mit SynchronizationContext.Send() gemacht.

public partial class MainWindow : Window
{
    private readonly SynchronizationContext _synchronizationContext;

    public MainWindow()
    {
        InitializeComponent();
        _synchronizationContext = SynchronizationContext.Current;
    }

    private void BtnStart_Click(object sender, RoutedEventArgs e)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolMethod));
    }
    private void ThreadPoolMethod(object state)
    {
        SetTimeUsingSynchronizationContext(DateTime.Now);
    }

    private void SetTimeUsingSynchronizationContext(DateTime dateTime)
    {
        _synchronizationContext.Post(SetDateTime, dateTime);
    }

    private void SetDateTime(object state)
    {
        DateTime dateTime = (DateTime)state;
        textBox.Text = dateTime.ToString();
    }
}

Follow up

Cross Thread Aufrufe sind eine Gefahr für das Multithreading, Datakorruption ist eine andere Gefahr, die wir im nächsten Blog anschauen werden.

← Vorige Post
Nächster Post →
Kommentare

2 Antworten zu “C# Concurrency Teil 5: Cross Thread Aufrufe”

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden

Copyright © 2025 Noser Engineering AG – Alle Rechte vorbehalten.

NACH OBEN
Privacy Policy Cookie Policy
Zur Webcast Übersicht