2009-01-19 4 views
11

Ich versuche derzeit, eine Komponente zu schreiben, wo einige Teile davon auf dem UI-Thread ausgeführt werden sollten (Erklärung wäre zu lang). Der einfachste Weg wäre also, ein Steuerelement an sie zu übergeben und InvokeRequired/Invoke dafür zu verwenden. Aber ich denke nicht, dass es ein guter Entwurf ist, eine Kontrollreferenz an eine "Daten/Hintergrund" -Komponente zu übergeben, also suche ich nach einer Möglichkeit, Code auf dem UI-Thread auszuführen, ohne ein Steuerelement zu haben verfügbar. So etwas wie Application.Dispatcher.Invoke in WPF ...Code auf UI-Thread ohne Steuerelement Objekt vorhanden

irgendwelche Ideen, thx Martin

+6

Bitte markieren eine Antwort als akzeptiert, wenn man Ihr Problem löst. – SandRock

Antwort

2

Sie haben recht, ist es nicht gut Kontrollen auf Threads zu übergeben. Winforms-Steuerelemente sind single-threaded, das Übergeben an mehrere Threads kann zu Race-Bedingungen führen oder die Benutzeroberfläche beschädigen. Stattdessen sollten Sie die Funktionen des Threads für die Benutzeroberfläche verfügbar machen und den Thread aufrufen, wenn die Benutzeroberfläche fertig und bereit ist. Wenn Hintergrundthreads Benutzeroberflächenänderungen auslösen sollen, legen Sie ein Hintergrundereignis offen und abonnieren Sie es über die Benutzeroberfläche. Der Thread kann Ereignisse auslösen, wann immer er möchte, und die Benutzeroberfläche kann auf sie reagieren, wenn dies möglich ist.

Das Erstellen dieser bidirektionalen Kommunikation zwischen Threads, die den UI-Thread nicht blockiert, ist eine Menge Arbeit. Hier eine gekürzte Beispiel eine Background-Klasse:

public class MyBackgroundThread : BackgroundWorker 
{ 
    public event EventHandler<ClassToPassToUI> IWantTheUIToDoSomething; 

    public MyStatus TheUIWantsToKnowThis { get { whatever... } } 

    public void TheUIWantsMeToDoSomething() 
    { 
     // Do something... 
    } 

    protected override void OnDoWork(DoWorkEventArgs e) 
    { 
     // This is called when the thread is started 
     while (!CancellationPending) 
     { 
      // The UI will set IWantTheUIToDoSomething when it is ready to do things. 
      if ((IWantTheUIToDoSomething != null) && IHaveUIData()) 
       IWantTheUIToDoSomething(this, new ClassToPassToUI(uiData)); 
     } 
    } 
} 


public partial class MyUIClass : Form 
{ 
    MyBackgroundThread backgroundThread; 

    delegate void ChangeUICallback(object sender, ClassToPassToUI uiData); 

    ... 

    public MyUIClass 
    { 
     backgroundThread = new MyBackgroundThread(); 

     // Do this when you're ready for requests from background threads: 
     backgroundThread.IWantTheUIToDoSomething += new EventHandler<ClassToPassToUI>(SomeoneWantsToChangeTheUI); 

     // This will run MyBackgroundThread.OnDoWork in a background thread: 
     backgroundThread.RunWorkerAsync(); 
    } 


    private void UserClickedAButtonOrSomething(object sender, EventArgs e) 
    { 
     // Really this should be done in the background thread, 
     // it is here as an example of calling a background task from the UI. 
     if (backgroundThread.TheUIWantsToKnowThis == MyStatus.ThreadIsInAStateToHandleUserRequests) 
      backgroundThread.TheUIWantsMeToDoSomething(); 

     // The UI can change the UI as well, this will not need marshalling. 
     SomeoneWantsToChangeTheUI(this, new ClassToPassToUI(localData)); 
    } 

    void SomeoneWantsToChangeTheUI(object sender, ClassToPassToUI uiData) 
    { 
     if (InvokeRequired) 
     { 
      // A background thread wants to change the UI. 
      if (iAmInAStateWhereTheUICanBeChanged) 
      { 
       var callback = new ChangeUICallback(SomeoneWantsToChangeTheUI); 
       Invoke(callback, new object[] { sender, uiData }); 
      } 
     } 
     else 
     { 
      // This is on the UI thread, either because it was called from the UI or was marshalled. 
      ChangeTheUI(uiData) 
     } 
    } 
} 
+1

Ich bevorzuge diese Methode, da sie den UI-Code sauber von der Hintergrundarbeit trennt. Außerdem können mehrere "Listener" auf Aktualisierungen des Arbeitsstatus im Hintergrund reagieren. –

1

Setzen Sie den UI-Manipulation in einem Verfahren auf dem Formular zu manipulierenden und einen Delegierten an den Code übergeben, die auf dem Hintergrund-Thread ausgeführt wird, à la APM. Sie müssen params object p nicht verwenden, Sie können es stark für Ihre eigenen Zwecke eingeben. Dies ist nur ein einfaches generisches Beispiel.

Dieser Ansatz basiert auf der Tatsache, dass ein Delegat auf eine Methode auf einer bestimmten Instanz verweist; Indem Sie die Implementierung zu einer Methode des Formulars machen, bringen Sie das Formular in den Geltungsbereich this. Das Folgende ist semantisch identisch.

delegate UiSafeCall(delegate d, params object p); 
void SomeUiSafeCall(delegate d, params object p) 
{ 
    if (this.InvokeRequired) 
    this.BeginInvoke(d,p);   
    else 
    { 
    //do stuff to UI 
    } 
} 
1

Was ist mit der Übergabe eines System.ComponentModel.ISynchronizeInvoke? Auf diese Weise können Sie vermeiden, ein Control zu übergeben.

18

Es gibt eine bessere, mehr abstrakte Weise, dies zu tun, die auf beiden WinForms und WPF funktioniert:

System.Threading.SynchronizationContext.Current.Post(theMethod, state); 

Das funktioniert, weil Windows installiert ein WindowsFormsSynchronizationContext Objekt als aktuellen Synchronisierungskontext. WPF tut etwas ähnliches und installiert seinen eigenen spezialisierten Synchronisationskontext (DispatcherSynchronizationContext).

entspricht control.BeginInvoke, und .Send entspricht control.Invoke.

+0

SynchronizationContext.Current ist null, wenn ich versuche, es aufzurufen ... D: –

+2

Zugriff auf SyncrhonizationContext.Current während des UI-Threads. Speichern Sie das für später, wenn Sie in einem anderen Thread sind. –

2

Halten Sie zuerst in Ihrem Formularkonstruktor einen klassenspezifischen Verweis auf das Objekt SynchronizationContext.Current (in Wirklichkeit ein WindowsFormsSynchronizationContext).

public partial class MyForm : Form { 
    private SynchronizationContext syncContext; 
    public MyForm() { 
     this.syncContext = SynchronizationContext.Current; 
    } 
} 

Dann überall in der Klasse, verwenden Sie diese Kontext-Nachrichten an die UI senden:

public partial class MyForm : Form { 
    public void DoStuff() { 
     ThreadPool.QueueUserWorkItem(_ => { 
      // worker thread starts 
      // invoke UI from here 
      this.syncContext.Send(() => 
       this.myButton.Text = "Updated from worker thread"); 
      // continue background work 
      this.syncContext.Send(() => { 
       this.myText1.Text = "Updated from worker thread"; 
       this.myText2.Text = "Updated from worker thread"; 
      }); 
      // continue background work 
     }); 
    } 
} 

Sie folgende Erweiterungsmethoden müssen mit Lambda-Ausdrücke arbeiten: http://codepaste.net/zje4k6