Mawin: C-Sharp: Zugriff durch Thread auf ein Objekt verweigert bei WPF

Heyho,
ich wusste nicht genau wo ich C# zuordnen sollte, ich hoffe es stört in diesem Themenbereich nicht.

Also:

Ich habe ein WPF-Projekt erstellt in der ich eine WPF-"login"-Page habe.

Die WPF-Page hat zwei TextBoxen (<TextBox>) (x:Name="username", x:Name="pw"), einen "Okay"-Button (<x:Name="ok">) und einen <TextBlock> (x:Name="status") in dem immer, falls was passiert, dies dann dort steht.

Wenn ich jetzt also auf den Button klicke, soll eine Methode aufgerufen werden die sozusagen das ganze Login behandelt.

Diese Methode befindet sich in der "Functions"-Klasse und sie hat als Übergabe-Parameter einen username-String, einen Passwort-String und die login page selber:

public class Functions
{
      public void anmelden(String username, String pw, login l)
      {
          //das ganze Login
      }
}

Diese eine Methode überprüft alles mögliche und ändert geg. den Status (im Textblock =====> <TextBlock x:Name="status"> =====> status.Text = neuerInhalt;).
Das würde dann in der Methode so ausschauen:

public void anmelden(String username, String pw, login l)
{
      //vorhin
      l.status.Text = "wasJetztPassiertIst";
      //danach
}

Es soll die GUI aktualisieren.
Die Methode an sich funktioniert:
(KLASSE DER LOGIN-PAGE:)

public partial class login : Page
    {
        Functions f = new Functions();
        threadlaeuft = false;//ob der Thread gerade laeuft oder nicht
        public login()
        {
            InitializeComponent();

}
        private void okayButtonWurdeGedrueckt(object sender, RoutedEventArgs e)
        {
           f.anmelden(username.Text,pw.Text,this);
        }
    }

Nur ist das Problem, dass die GUI einfriert bis die Methode zu Ende ausgeführt worden ist und nur sich dann erst aktualisiert.

Gut. Das Problem kenne ich schon von Java (GUI einfrieren, wenn die Methode länger braucht),
also habe ich mich über Threads erkundigt und bin auf den BackgroundWorker gekommen.
(Quelle: http://stackoverflow.com/questions/19928956/c-sharp-wpf-dispatcher-thread )

Also schaut die Methode jetzt so aus:

private void okayButtonWurdeGedrueckt(object sender, RoutedEventArgs e)
{
   var worker = new BackgroundWorker();
   worker.DoWork += (x, y) =>
   {
       f.anmelden(username.Text,pw.Text,this);
       threadlaeuft = false; //Der Thread ist zu Ende
   };
   worker.RunWorkerAsync();
   while(true)//damit er weiß, dass der Thread noch läuft oder auch nicht
   {
       Thread.Sleep(1);
       if (threadlaeuft==false) //dann ist der Thread vorbei.
       {
           break;
       }
   }
}

Nun mein Problem:

Ich starte die Anwendung, fülle Werte die TextBoxen ein und drücke den Button.
Alles ist in Ordnung nur beim
f.anmelden(username.Text,pw.Text,this);
Teil wirft er beim "this" eine "InvalidOperationException" mit dem Text:

"Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet."

Ja, im "this" (also der Login-Page) wird der Gui-Update Thread aufgerufen, somit darf ich von dem nicht auf das "this" (der Login-Page) zugreifen.
Aber wie soll ich sonst aus dem "this" ein bestimmtes Objekt rausbekommen (textbox) und genau dieses Aktualisieren (Inhalt)?

Ich habe auch die
new Thread(new ThreadStart(starteAnmeldung));
new Thread(new ParameterizedThreadStart(stateAnmeldung));
Möglichkeiten ausprobiert, aber immer, wenn er zum 'this' kommt, kommt diese Fehlermeldung.

Wie kann ich das Umgehen?

LG Mawin

  1. Moin,

    auf alle Oberflächenelemente sollte unter Windows aus dem Main-Thread zugegriffen werden. Nur dieser hat erstmal eine Message-Queue. Das heißt, die Elemente kommunizieren über Nachrichten und die Message-Queue ist sozusgagen der große Nachrichtensammeltopf (siehe PeekMessage, PumpMessage etc.). Wenn die Queue nicht abgearbeitet werden kann, dann friert deine Oberfläche ein. Ab Vista werden von Windows auch so Kontrollnachrichten versandt, um nachzuschau'n, ob die Anwendung noch reagiert (siehe ghost window, das hat bestimmt schon jeder gesehen).

    Jetzt basiert WPF ja auf einen Visual-Three, aber ich denke auch dort trifft im Grunde das bereits genannte zu. Daher sollte deine Methode nicht ewig lang blockieren oder warten, weil ja sonst die Message-Queue nicht abgearbeitet wird.

    Wenn du jetzt in einem anderen Thread auf die Oberflächenelemente zugreifen willst, dann solltest du das via Invoke machen, also die Aufgabe an den Main-Thread weiterleiten. Alles andere führt sonst zu ungewollten Ergebnissen (z.B. Seiteneffekte).

    Beispiel Lösungsansatz (aus dem Kopf heraus):

      
    protected void OnButtonOk(object sender, EventArgs e)  
    {  
      Task.Factory.StartNew(() => {  
        login(...);                                            // Im Hintergrund  
        this.Invoke(new Action(() => textbox.text = "hallo")); // GUI aktualisieren  
      });  
    }  
    
    

    Du kannst auch mal nach InvokeRequired suchen, da wird auch so einiges erklärt, wie das zusammenhängt. Obiges Beispiel kannst du auch auf den BackgroundWorker ummünzen (der ist auch ein Hintergrund-Thread).

    hth

    1. Danke für die Antwort!
      Nur weiß ich nicht genau, wie ich von dem Main-Thread auf die Login-Seite zugreifen kann:
      Also: Es gibt ein Hauptwindow und in diesem wird dann die Login-Page eingebunden.
      Wie kann ich jetzt von dem Hauptwindow auf diese untere Page zugreifen?

      Die Invoke Methode habe ich nicht in meiner login-Page, noch in sonstigen controls gefunden und dann bin ich im Internet auf Dispatcher gekommen,
      nur ladet jetzt die Anwendung ewig und friert ein weil jetzt das f.anmelden(...) gar nicht mehr aufgerufen wird.

      Das "threadlaeuft = false;" wird in der f.anmelden(...)-Methode gesetzt, da beim Invoke nur ein Befehl ausgeführt werden darf.

      Da aber dank der Dispatcher.Invoke die Methode gar nicht erst gestartet wird und somit kein
      "threadlaeuft = false;" stattfindet, läuft der

      while (threadlaeuft)
      {
           Thread.Sleep(100);
      }

      immer weiter und somit passiert nichts und die GUI friert ein!

      Mein Code (bei beiden das Gleiche Problem):

        
       var worker = new BackgroundWorker();  
       worker.DoWork += (x,y) =>  
       {  
          Dispatcher.Invoke( new Action( () =>  f.anmelden(username.Text,pw.Text,this)  
                               ));  
        
        
                          };  
         worker.RunWorkerAsync();  
      
      
        
      Task.Factory.StartNew(() => {  
        Dispatcher.Invoke(new Action(() => f.anmelden(username.Text,pw.Text,this)));  
      });  
      
      
      1. Moin,

        ..., da beim Invoke nur ein Befehl ausgeführt werden darf.

        ehm ja eine Aktion, aber darin kannst du ja mehrere Anweisungen unterbringen.

          
          Task.Factory.StartNew(() => {  
            Dispatcher.Invoke(new Action(() => {  
              f.anmelden(username.Text,pw.Text,this);  
              wasauchimmer();  
              undsoweiter();  
            }));  
          });  
        
        

        Da aber dank der Dispatcher.Invoke die Methode gar nicht erst gestartet wird und somit kein
        "threadlaeuft = false;" stattfindet, läuft der

        while (threadlaeuft)
        {
             Thread.Sleep(100);
        }

        genau diese Stelle blockiert ja auch den Main-Thread und ich weiß auch gar nicht, warum du das hier so machst. Dispatcher ist gut, glaube das ist das WPF-Pedant zu der Forms-Invoke Geschichte.

        Jedenfalls kann die Aufgabe nicht an den Main-Thread überreicht werden, weil der ja durch deine while-Schleife und Thread.Sleep blockiert wird.

        So ganz genau habe ich jetzt noch nicht verstanden, was du mit "Login-Seite-Einbinden" meinst. In WPF deklarierst du ja deine Oberfläche mit all den Elementen (xaml) und bindest sie an Felder oder was auch immer aus deiner Klasse. Bei Änderungen der Felder in deiner Klasse werden die Oberflächenelemente automatisch aktualisiert und/oder auch umgekhert (TwoWayBinding etc. sowie auch DependencyProperty, sind jetzt nur Stichwörter zum Suchen).

        Nimm jetzt einfach mal die while-Schleife raus und reduziere die Anmelden-Methode auf ein Minimum. Was macht die eigentlich genau? Schaffst du mit MVVP und Webdiensten? Na wie auch immer.

        1. while + Sleep => raus
        2. anmelden() => reduzieren

        PS: Bei WPF ist es sinnvoll, nicht direkt auf die Oberflächenelemente zuzugreifen. Ändere Werte deines Modells und binde das Modell an deine Deklaration für die Oberfläche. Das ist fast so wie Javascript und CSS. Man hat es getrennt.

        Gruß

        1. Hi,
          sorry ich war leider weg, aber nun:

          Danke! Also:

          Das ganze Programm selber soll sozusagen ein Server-Client Programm sein.
          Also: Man gibt in den Textfeldern die ganzen Informationen ein und nach einem Klick auf den Okay-Button schickt das Programm die ganzen Informationen verschlüsselt an den Server und bekommt die Antwort als XML-Format. Diese Wertet das Programm dann aus und je nachdem ob das Login erfolgreich war oder nicht kommt dann halt eine Meldung oder man wird wieder zurück zum Login gebracht.

          Während die Antwort des Servers erwartet wird soll da ein Ladebalken sein und ein Textblock wo der Text halt immer aktualisiert werden soll (der Momentane Status, also z.B.: "Verbinde...","Erfolgreich Verbunden!","Logge ein...","Lade Informationen herunter...",... usw.).

          1. anmelden() => reduzieren

          Ich habe schon die Anmelde Methode soweit reduziert wie es nur geht aber das Problem ist:
          Ich kann ja nicht wissen wie lange es dauert bis der Server antwortet.

          Den

          while (threadlaeuft)
          {
               Thread.Sleep(100);
          }

          -Teil (ist jetzt entfernt) habe ich unabsichtlich gemacht, weil das f.anmelden(username.Text,pw.Text,this);
          eigentlich einen Rückgabe-Wert liefert (ist der Rückgabewert null so ist das Login fehlgeschlagen, ist er es nicht, wird das Ergebnis ausgearbeitet) was auch der Grund ist warum ich die Schleife gemacht habe: Damit er genau das macht! (Rückgabewerte überprüfen)
          Ich habe diese Überprüfungen nun aber in den Thread eingebaut, damit die GUI nicht einfriert.

            
          Task.Factory.StartNew(() => {  
          Dispatcher.Invoke(new Action(() => threadMethode() ));  
          });  
          
          

          (Die threadMethode() ruft das f.anmelden() auf und wertet auch gleich das Ergebnis aus [ist aber alles in einem anderen Thread, wodurch der Hauptthread und die GUI nicht einfrieren sollten].)

          Nun ist nur noch folgendes Problem: Die Schleife ist raus. Die letzte Methode die im Hauptthread aufgerufen wird ist die, die den Thread startet.
          Irgendwie friert aber jetzt komischerweise der zweite Thread die GUI vom Hauptthread ein!

          PS: Bei WPF ist es sinnvoll, nicht direkt auf die Oberflächenelemente zuzugreifen. Ändere
          Werte deines Modells und binde das Modell an deine Deklaration für die Oberfläche. Das ist
          fast so wie Javascript und CSS. Man hat es getrennt.

          Ich habe mich erkundigt und jemand hat mir vorgeschlagen das Ganze mit einem Binding zu machen. So würde ich nicht direkt auf die Oberflächenelemente zugreifen:

            
          <TextBlock FontSize="12" Text="{Binding Source={StaticResource status}, Path=Status}" />  
            
            
          <!-- Damit würde ich jetzt den Inhalt ändern: -->  
          <Window.Resources>  
          <ObjectDataProvider x:Key="status" ObjectType="{x:Type local:Statusklasse}" />  
          </Window.Resources>  
          
          

          Die Statusklasse:

            
          class Statusklasse : INotifyPropertyChanged  
          {  
              public event PropertyChangedEventHandler PropertyChanged;  
              private String status;  
              public String Status  
              {  
                 get { return status; }  
                 set  
                 {  
                       now = value;  
                       if (PropertyChanged != null)  
                              PropertyChanged(this, new PropertyChangedEventArgs("Status"));  
                  }  
              }  
            
              ...  
          
          

          Nur ist das Problem:
          Ein Timer wird öfters ausgeführt. Ich will ihn aber nur einmal starten (genau dann, wenn ich auf den Button drücke!).
          Also wurde mir der Backgroundworker vorgeschlagen, aber der soll nur für kurze Ausführungen genutzt werden!

          Die anmelde-Methode verbindet sich öfters mit den Server. (Zuerst meldet das Programm sich am Server an, dann ladet es die Serverinformationen herunter und dann ein Bild vom Server: Also 3 mal insgesamt).

          Die Person hat mir dann vorgeschlagen irgendwelche asyncron-Methoden vom WebClient zu benutzen, die dann Melden, wenn der Server antwortet.

          So zerlegt sich dann aber die ursprüngliche Methode (f.anmelden(username.Text,pw.Text,this);) in lauter kleine Methoden!

          Kann ich nicht einfach eine EINZIGE Methode machen, die sich um alles kümmert?
          (
          Verbinden, Antwort vom Server bearbeiten, GUI aktualisieren,
          Verbinden, Antwort vom Server bearbeiten, GUI aktualisieren,
          ...)

          Was soll ich machen?

          LG Mawin

          1. Moin,

            sorry ich war leider weg, aber nun:

            dachte schon, ich wäre dir nicht gut genug ^^. Aber gut, ich bin auch nicht der Super-Guru für WPF.

            Multithreading ist ein Thema für sich, da braucht mal viel Hintergrundwissen. Wenn es um C# geht, dann muss man auch noch wissen was die (MS) da eigentlich mit den entspr. Funktionen bezwecken und es ist viel Lesearbeit nötig. Ich habe das jedenfalls nicht alles im Kopf aber im Hinterkopf, dass da irgendwas war. So nun ...

            Ein Timer wird öfters ausgeführt.

            Es gibt verschiedene Timer-Klassen. Allen gemein ist meistens, dass sie auslösen, wenn ein Intervall überschritten wurde. Dieses automatische Auslösen kann man durch einen Aufruf der entsprechenden Stop-Methode unterbinden oder indem man die Eigenschaft "AutoReset" auf false setzt. Das ist entsprechend der Timer-Klasse nachzulesen. Windows-Forms-Timer haben zudem ein minimales Intervall von 55ms und es kommt auch darauf an, in welchem Kontext dann die Callback-Funktion ausgeführt wird. Aber das ist jetzt nur zur Anmerkung, warum nutzt du einen Timer?

            Sinnvollerweise änderst du Daten in deinem Model. Über die Bindung wird die UI davon automatisch benachrichtigt ... egal.

            Task.Factory.StartNew(() => {
            Dispatcher.Invoke(new Action(() => threadMethode() ));
            });

            
            > (Die threadMethode() ruft das f.anmelden() auf und wertet auch gleich das Ergebnis aus [ist aber alles in einem anderen Thread, wodurch der Hauptthread und die GUI nicht einfrieren sollten].)  
              
            Das ist ein Schuß von hinten durch die Brust ins Auge. "Dispatcher.Invoke" sorgt ja dafür, dass die Aktion in seinem Kontext (Hauptthread) ausgeführt wird (und blockiert). Dafür brauchst du keinen Thread starten, der die Aktion wieder reflektiert. Den Task solltest du nur für Hintergrundarbeiten anschmeißen. Also Webservice aufrufen und Ergebnis liefern. Danach das Model anpassen, also die Änderungen speichern. Die UI wird automatisch durch die Bindung zu deinem Model aktualisiert. Das Model bzw. Eigenschaften davon sollten via "Dispatcher-Invoke" aktualisiert werden (würde ich jetzt empfehlen).  
              
            ~~~c++
              
              Task.Factory.StartNew(() => {  
                var result = longrunningtask();  
                model.Status = (result == null ? "Fehler" : "Ok");  
              }, TaskCreationOptions.LongRunning);  
            
            

            oder auch

              
              Task.Factory.StartNew(() => {  
                 var result = longrunningtask();  
                 Dispatcher.Invoke(() => {  
                   model.Status = (result == null ? "Fehler" : "Ok");  
                 });  
              }, TaskCreationOptions.LongRunning);  
            
            

            Sollte f.anmelden() ansich auf das Model zugreifen, dann ist das ein Fehler im Design. Dafür kannst du aber nichts, das wurde jahrelang so propagiert.

            1. So habe mich mal rangesetzt und eine kleine Demo-Anwendung geschrieben, die sollte weiterhelfen. Einfach ein neues WPF-Projekt erstellen (WpfDemo) und folgende Daten entsprechend einfügen. Das Fenster friert nicht ein, im Hintergrund läuft ein Thread, der dann hin- und wieder Statustext ausgibt.

                
              <Window x:Name="window" x:Class="WpfDemo.MainWindow"  
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
                      Title="MainWindow" Height="286" Width="335" ResizeMode="NoResize" UseLayoutRounding="True">  
                <Grid Margin="10,10,10,10">  
                  <Button Content="Start" HorizontalAlignment="Left" Margin="216,204,0,0" VerticalAlignment="Top" Width="79" Height="24" IsEnabled="{Binding AllowInput, ElementName=window, Mode=OneWay, ValidatesOnNotifyDataErrors=False}" Click="Button_Click"/>  
                  <TextBox HorizontalAlignment="Left" Height="23" Margin="10,16,0,0" TextWrapping="Wrap" Text="{Binding Message, ElementName=window, Mode=TwoWay, ValidatesOnNotifyDataErrors=False}" VerticalAlignment="Top" Width="289" IsEnabled="{Binding AllowInput, ElementName=window, Mode=OneWay, ValidatesOnNotifyDataErrors=False}"/>  
                  <Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" Height="138" Margin="10,61,0,0" VerticalAlignment="Top" Width="289">  
                    <TextBlock HorizontalAlignment="Left" Margin="10,10,10,10" TextWrapping="Wrap" Text="{Binding StatusText, ElementName=window, Mode=OneWay, ValidatesOnNotifyDataErrors=False}" VerticalAlignment="Top" />  
                  </Border>  
                </Grid>  
              </Window>  
              
              
                
              using System;  
              using System.Collections.Generic;  
              using System.Linq;  
              using System.Text;  
              using System.Threading;  
              using System.Threading.Tasks;  
              using System.Windows;  
              using System.Windows.Controls;  
              using System.Windows.Data;  
              using System.Windows.Documents;  
              using System.Windows.Input;  
              using System.Windows.Media;  
              using System.Windows.Media.Imaging;  
              using System.Windows.Navigation;  
              using System.Windows.Shapes;  
                
              namespace WpfDemo  
              {  
                /// <summary>  
                /// Interaktionslogik für MainWindow.xaml  
                /// </summary>  
                public partial class MainWindow : Window  
                {  
                  // Design-Time  
                  public static readonly DependencyProperty AllowInputProperty = DependencyProperty.Register("AllowInput", typeof(bool), typeof(MainWindow));  
                  public static readonly DependencyProperty MesssageProperty = DependencyProperty.Register("Message", typeof(string), typeof(MainWindow));  
                  public static readonly DependencyProperty StatusTextProperty = DependencyProperty.Register("StatusText", typeof(string), typeof(MainWindow));  
                
                  internal bool AllowInput  
                  {  
                    get { return (bool)GetValue(AllowInputProperty); }  
                    set { SetValue(AllowInputProperty, value); }  
                  }  
                
                  internal string Message  
                  {  
                    get { return (string)GetValue(MesssageProperty); }  
                    set { SetValue(MesssageProperty, value); }  
                  }  
                
                  internal string StatusText  
                  {  
                    get { return (string)GetValue(StatusTextProperty); }  
                    set { SetValue(StatusTextProperty, value); }  
                  }  
                
                  public MainWindow()  
                  {  
                    InitializeComponent();  
                    AllowInput = true;  
                    Message = "Hier Text eingeben";  
                    StatusText = "";  
                  }  
                
                  private void Button_Click(object sender, RoutedEventArgs e)  
                  {  
                    AllowInput = false;  
                    StatusText = "";  
                
                    Task.Factory.StartNew(() =>  
                    {  
                      try  
                      {  
                        for (int i = 0; i < 5; ++i)  
                        {  
                          Thread.Sleep(1000); // oder was auch immer  
                          InvokeUI(() => StatusText += string.Format("{0}: {1}{2}", DateTime.Now, Message, Environment.NewLine)); // Sicher  
                        }  
                
                        throw new Exception("Das ist ein Test");  
                      }  
                      catch (Exception ex)  
                      {  
                        Thread.Sleep(1000);  
                        AddStatusText(ex.Message); // Sicher  
                      }  
                
                      InvokeUI(() => AllowInput = true); // Sicher  
                    }, TaskCreationOptions.LongRunning);  
                  }  
                
                  private void InvokeUI(Action action)  
                  {  
                    Dispatcher.Invoke(action);  
                  }  
                
                  private void AddStatusText(string message)  
                  {  
                    InvokeUI(() => StatusText += string.Format("{0}: {1}{2}", DateTime.Now, message, Environment.NewLine));  
                  }  
                }  
              }  
              
              
            2. Hi,
              danke für dieses Beispiel!
              So ein Beispiel ist genau das was ich gesucht habe :D

              LG Mawin

              Moin,

              sorry ich war leider weg, aber nun:

              dachte schon, ich wäre dir nicht gut genug ^^. Aber gut, ich bin auch nicht der Super-Guru für WPF.

              Multithreading ist ein Thema für sich, da braucht mal viel Hintergrundwissen. Wenn es um C# geht, dann muss man auch noch wissen was die (MS) da eigentlich mit den entspr. Funktionen bezwecken und es ist viel Lesearbeit nötig. Ich habe das jedenfalls nicht alles im Kopf aber im Hinterkopf, dass da irgendwas war. So nun ...

              Ein Timer wird öfters ausgeführt.

              Es gibt verschiedene Timer-Klassen. Allen gemein ist meistens, dass sie auslösen, wenn ein Intervall überschritten wurde. Dieses automatische Auslösen kann man durch einen Aufruf der entsprechenden Stop-Methode unterbinden oder indem man die Eigenschaft "AutoReset" auf false setzt. Das ist entsprechend der Timer-Klasse nachzulesen. Windows-Forms-Timer haben zudem ein minimales Intervall von 55ms und es kommt auch darauf an, in welchem Kontext dann die Callback-Funktion ausgeführt wird. Aber das ist jetzt nur zur Anmerkung, warum nutzt du einen Timer?

              Sinnvollerweise änderst du Daten in deinem Model. Über die Bindung wird die UI davon automatisch benachrichtigt ... egal.

              Task.Factory.StartNew(() => {
              Dispatcher.Invoke(new Action(() => threadMethode() ));
              });

              
              > > (Die threadMethode() ruft das f.anmelden() auf und wertet auch gleich das Ergebnis aus [ist aber alles in einem anderen Thread, wodurch der Hauptthread und die GUI nicht einfrieren sollten].)  
              >   
              > Das ist ein Schuß von hinten durch die Brust ins Auge. "Dispatcher.Invoke" sorgt ja dafür, dass die Aktion in seinem Kontext (Hauptthread) ausgeführt wird (und blockiert). Dafür brauchst du keinen Thread starten, der die Aktion wieder reflektiert. Den Task solltest du nur für Hintergrundarbeiten anschmeißen. Also Webservice aufrufen und Ergebnis liefern. Danach das Model anpassen, also die Änderungen speichern. Die UI wird automatisch durch die Bindung zu deinem Model aktualisiert. Das Model bzw. Eigenschaften davon sollten via "Dispatcher-Invoke" aktualisiert werden (würde ich jetzt empfehlen).  
              >   
              > ~~~c++
                
              
              >   Task.Factory.StartNew(() => {  
              >     var result = longrunningtask();  
              >     model.Status = (result == null ? "Fehler" : "Ok");  
              >   }, TaskCreationOptions.LongRunning);  
              > 
              
              

              oder auch

              Task.Factory.StartNew(() => {
                   var result = longrunningtask();
                   Dispatcher.Invoke(() => {
                     model.Status = (result == null ? "Fehler" : "Ok");
                   });
                }, TaskCreationOptions.LongRunning);

              
              >   
              > Sollte f.anmelden() ansich auf das Model zugreifen, dann ist das ein Fehler im Design. Dafür kannst du aber nichts, das wurde jahrelang so propagiert.  
              >   
              >   
              > 
              
          2. Gleich mal vorweg, ich habe keine Ahnung von C#!

            Soweit ich dich verstanden habe, willst du folgendes:
            * Du hast deinen Mainthread. In diesem läuft die MsgQueue für deine GUI.
            * Du hast einen Thread, der soll etwas machen.
            * Auf das Ergebnis soll der Mainthread warten.
            * Du willst zwischendurch aus dem Workerthread eine Statusanzeige aktualisieren.

            Wie man das Threadobjekt anlegt und threadsicher mit Daten versorgt gehe ich jetzt nicht ein, da dass erst mal nicht dein Problem zu sein scheint. Dein Problem ist, soweit ich das verstanden habe, dass deine GUI einfriert.

            In C++ würde man das folgendermaßen machen(ich gehe hier speziell auf die Windows-Api Funktionen ein, für die es ja auch eine Umsetzung in C# geben muß):
            Du baust dir ein CallbackObjekt

              
            class Callback  
            {  
            public:  
              inline Callback()  
              {  
                m_finish = [link:http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396%28v=vs.85%29.aspx@title=CreateEvent](NULL, TRUE, FALSE,  NULL);  
              }  
              
            public:  
              inline void waitForFinish()  
              {  
                [link:http://msdn.microsoft.com/de-de/library/26hwk2bx.aspx@title=AtlWaitWithMessageLoop](m_finish);  
                [link:http://msdn.microsoft.com/en-us/library/windows/desktop/ms685081%28v=vs.85%29.aspx@title=ResetEvent](m_finish);  
              }  
              inline void updateStatus(String status)  
              {  
                // status anzeigen  
              }  
              inline void finish(String status)  
              {  
                [link:http://msdn.microsoft.com/en-us/library/windows/desktop/ms686211%28v=vs.85%29.aspx@title=SetEvent](m_finish);  
              }  
            private:  
              HANDLE m_finish;  
            };
            

            Das sind alles API-Funktionen bis auf AtlWaitWithMessageLoop, also müsstest du da eigentlich auch von C# irgendwie darauf zugreifen können und das entsprechend umsetzen können.
            AtlWaitWithMessageLoop macht jetzt auch nichts anderes als ein MsgWaitForMultipleObjects in einer Endlosschleife mit dwWakeMask = QS_ALLINPUT.
            Geht dein Event auf signaled, also MsgWaitForMultipleObjects gibt WAIT_OBJECT_0 zurück, wird die Endlosschleife verlassen. Wurde MsgWaitForMultipleObjects wegen einer neuen Msg in der Queue beendet, wird die MsgQueue bearbeitet.

            while([link:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943%28v=vs.85%29.aspx@title=PeekMessage](&msg,NULL,NULL,NULL,PM_REMOVE))  
            {  
              [link:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644955%28v=vs.85%29.aspx@title=TranslateMessage](&msg);  
              [link:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644934%28v=vs.85%29.aspx@title=DispatchMessage](&msg);  
            }
            

            Zusätzlich wird in dieser Schleife noch überprüft (ohne zu warten),  ob das Event zwischenzeitlich auf signaled gegangen ist, also sieht diese eigentlich so aus:

              
            while(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))  
            {  
              TranslateMessage(&msg);  
              DispatchMessage(&msg);  
              if ([link:http://msdn.microsoft.com/en-us/library/windows/desktop/ms687032%28v=vs.85%29.aspx@title=WaitForSingleObject](hEvent, 0) == WAIT_OBJECT_0)  
                return TRUE; // Event is now signaled.  
            }  
            
            

            Hier nochmal die ganze Funktion:

            ATLINLINE ATLAPI_(BOOL) AtlWaitWithMessageLoop(HANDLE hEvent)  
            {  
            	DWORD dwRet;  
            	MSG msg;  
              
            	while(1)  
            	{  
            		dwRet = MsgWaitForMultipleObjects(1, &hEvent, FALSE, INFINITE, QS_ALLINPUT);  
              
            		if (dwRet == WAIT_OBJECT_0)  
            			return TRUE;    // The event was signaled  
              
            		if (dwRet != WAIT_OBJECT_0 + 1)  
            			break;          // Something else happened  
              
            		// There is one or more window message available. Dispatch them  
            		while(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))  
            		{  
            			TranslateMessage(&msg);  
            			DispatchMessage(&msg);  
            			if (WaitForSingleObject(hEvent, 0) == WAIT_OBJECT_0)  
            				return TRUE; // Event is now signaled.  
            		}  
            	}  
            	return FALSE;  
            }
            

            Damit ist alles auf Windows-API-Aufrufe aufgelöst.

            Jetzt zum Ablauf:
            * du hast irgendwo dein Threadobjekt
            * du rufst im Mainthread deine Funktion auf
            * legst dort CallbackObjekt an
            * übergibst dieses zusammen mit deinen benötigten Daten an die Funktion des Threadobjektes, welche deine Daten und das CallbackObjekt(nicht als Kopie, sondern als Pointer/Referenz) speichert und den Thread startet
            (* oder startest den Thread in einem weiteren Funktionsaufruf an deinem Threadobjekt)
            * du rufst in deiner Funktion im Mainthread CallbackObjekt->waitForFinish() auf
            * dein Mainthread legt sich schlafen und wartet auf das Event oder eine Msg
            Im Thread:
            * du verarbeitest deine Daten
            * rufst am gespeicherten CallbackObjekt bei bedarf updateStatus auf
            * updateStatus wird im Context des Workerthreads ausgeführt, aber alle Gui-Änderungen erfolgen über Msg im Mainthread
            * machst du nun eine solche, wacht dein Mainthread auf, verarbeitet diese und legt sich wieder schlafen
            * sind alle Daten im Workerthread verarbeitet, rufst du am CallbackObjekt finish auf

            Das finish setzt das Event signaled, dein Mainthread wacht auf und die Endlosschleife wird beendet wodurch deine Funktion im Mainthread weiterläuft/zu Ende kommt.