2009-01-19 12 views
11

Obecnie próbuję napisać komponent, w którym niektóre jego części powinny działać w wątku UI (wyjaśnienie byłoby długie). Tak więc najprostszym sposobem byłoby przekazanie do niego kontroli i użycie InvokeRequired/Invoke na tym. Ale nie sądzę, że to dobry projekt, aby przekazać referencję kontrolną do komponentu "data/tło", więc szukam sposobu na uruchomienie kodu w wątku UI bez potrzeby posiadania kontroli dostępny. Coś Application.Dispatcher.Invoke w WPF ...Uruchom kod na wątku UI bez obiektu kontrolnego obecny

jakieś pomysły, thx Martin

+6

zaznaczyć proszę odpowiedź jako zaakceptowana, jeśli rozwiążesz problem. – SandRock

Odpowiedz

2

Masz rację, to nie jest dobry, aby przejść do kontroli wątków. Formanty WinForm są jednowątkowe, przekazywanie ich do wielu wątków może powodować warunki wyścigu lub złamać twój interfejs użytkownika. Zamiast tego powinieneś udostępnić funkcje wątku interfejsowi użytkownika i pozwolić mu wywołać wątek, gdy interfejs jest dobry i gotowy. Jeśli chcesz, aby wątki w tle wyzwalały zmiany w interfejsie użytkownika, ujawnij wydarzenie tła i zasubskrybuj je z interfejsu użytkownika. Wątek może uruchamiać zdarzenia w dowolnym momencie, a interfejs użytkownika może na nie reagować, gdy jest to możliwe.

Tworzenie tej dwukierunkowej komunikacji między wątkami, która nie blokuje wątku UI, wymaga dużo pracy. Tutaj jest bardzo skrócona przykład przy użyciu klasy BackgroundWorker:

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

Bardzo preferuję tę metodę, ponieważ w czysty sposób oddziela kod UI od pracy w tle. Pozwala także wielu "słuchaczom" reagować na aktualizacje statusu pracy w tle. –

1

Put manipulacji UI w metodzie na formularzu być manipulowane i przekazać delegata do kodu działającego w wątku tła, à la APM. Nie musisz używać params object p, możesz zdecydowanie napisać, aby pasował do własnych celów. To tylko zwykła próbka generyczna.

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

To podejście opiera się na fakcie, że pełnomocnik dotyczy sposobu, w szczególnym przypadku; poprzez implementację metody formularza, wprowadzasz formę do zakresu jako this. Poniższe jest semantycznie identyczne.

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

Co z przekazywaniem System.ComponentModel.ISynchronizeInvoke? W ten sposób unikniesz przejęcia kontroli.

18

Jest lepiej, bardziej abstrakcyjny sposób, aby to zrobić, który działa na obu WinForms i WPF:

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

To działa, ponieważ WindowsForms instaluje WindowsFormsSynchronizationContext obiekt jako bieżącym kontekście synchronizacji. WPF robi coś podobnego, instalując własny specjalistyczny kontekst synchronizacji (DispatcherSynchronizationContext).

.Post odpowiada control.BeginInvoke, a .Send odpowiada control.Invoke.

+0

SynchronizationContext.Current ma wartość NULL, gdy próbuję go nazwać ... D: –

+2

Dostęp do SyncrhonizationContext.Current podczas wątku interfejsu użytkownika. Zapisz to na później, kiedy jesteś w innym wątku. –

2

Po pierwsze, w swoim konstruktorze formularzy zachowaj odwołanie o klasie w odniesieniu do obiektu SynchronizationContext.Current (który jest w rzeczywistości WindowsFormsSynchronizationContext).

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

Następnie, w dowolnym miejscu w swojej klasie, należy użyć tego kontekstu, aby wysyłać wiadomości do Ui:

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 
     }); 
    } 
} 

trzeba będzie następujące metody rozszerzenie do pracy z wyrażeń lambda: http://codepaste.net/zje4k6

Powiązane problemy