2010-12-13 15 views
9

Mam przycisk w mojej postaci okna, który wywołuje metodę RunWorkerAsync(), która z kolei wykonuje akcję, która następnie aktualizuje ListBox w tym samym formularzu.Uzyskiwanie dostępu do kontroli interfejsu użytkownika z wątku BackgroundWorker

po zdarzeniu DoWork zakończył przypisać wynik dla przypadku (co jest lista), I przetwarzać() zdarzenie RunWorkerCompleted a następnie wykonać następujący kod do aktualizacji mój Listbox

alt text

który nazywa to:

alt text

(Przepraszam, formatowania kodu nie będzie działać)

Teraz kiedy uruchomić aplikację i nacisnąć przycisk odświeżania następujący wyjątek pojawia:

alt text

Jak bym to obejść?

Edit:

Wyjątkiem jest wyrzucane na folowing oświadczeniu, to występuje w metodzie DoWork gdzie wyczyścić zawartość, aby wykaz na bieżąco;

listBoxServers.Items.Clear();

+0

Czy to jest WPF lub Windows Forms? I na której linii znajduje się ten wyjątek? – decyclone

+0

@decyclone Zaktualizowałem moje pytanie z więcej informacji –

+0

Polecam odpowiedź Chrisa. – decyclone

Odpowiedz

10

Oto fragment, który uważam za bardzo przydatny:

public static void ThreadSafe(Action action) 
{ 
    Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, 
     new MethodInvoker(action)); 
} 

Można go przejść każdy delegat Action typu lambda lub po prostu tak:

ThreadSafe(() => 
{ 
    [your code here] 
}); 

lub

ThreadSafe(listBoxServers.Items.Clear); 
+1

Czy to nie jest WPF? Czy WinForms obsługuje Dispatchera? –

+0

'Dispatcher' jest klasą WPF, ale ten kod będzie działał z WinForm. – Nobody

+0

Z jakiegoś powodu Dispatcher nie jest rozpoznawany przez IntelliSense, mam dołączone oświadczenie System.Threading. –

1

Wątki w tle nie mogą aktualizować interfejsu użytkownika w aplikacjach systemu Windows, dlatego należy przywrócić kontrolę z powrotem do wątku interfejsu użytkownika dla rzeczywistej aktualizacji.

Utwórz metodę, która będzie nazywać UpdateServerDetails w głównym wątku, tak:

private void DispatchServerDetails(List<ServerDetails> details) 
{ 
    Action<List<ServerDetails>> action = UpdateServerDetails; 
    Dispatcher.Invoke(action) 
} 

a następnie zadzwonić DispatchServerDetails zamiast UpdateServerDetails.

Niektóre Ostrzeżenia:
-To działa najlepiej w aplikacji WPF, na WinForms, trzeba skakać przez kilka obręcze, lub użyć InvokeRequired
-The UI aktualizacja jest nadal synchroniczny, więc jeśli UpdateServerDetails robi dużo pracy, to zablokuje wątek UI (nie twój przypadek, tylko po to, by być po bezpiecznej stronie).

+0

Czy możesz podać przykład? –

+0

@Jamie http://stackoverflow.com/questions/975087/wpf-dispatcher-and-running-it-in-background – jan

+1

Czy to nie jest klasa WPF? – CodesInChaos

9

Co zrobiłam coś tak jak to za każdym razem trzeba uruchomić coś w całej wątków:

listBoxServers.BeginInvoke(
    (Action) 
    (() => listBoxServers.Items.Clear())); 
+0

Typowy problem Lispa, wydaje się, że brakuje paren. –

+0

Dzięki Hans, dodałem brakujący paren. –

13

Nie możesz zadzwonić pod numer Invoke w polu listy, ale w formularzu.Dla aplikacji WinForms używać coś takiego:

... 
this.Invoke((MethodInvoker)delegate() 
{ 
    // Do stuff on ANY control on the form. 
}); 
... 

zależności od wersji .NET, być może trzeba będzie zadeklarować delegata dla MethodInvoker siebie jako

public delegate void MethodInvoker(); 

Jednakże, można również rozważyć użycie funkcji ReportProgress pracownika działającego w tle. Odpowiednią procedurę obsługi zdarzenia należy wywołać w kontekście wątku formularza.

1

Korzystanie z Invoke w projekcie formularzy okien może być nieco trudne, istnieją pewne pułapki, które są udokumentowane, ale łatwe do przeoczenia. Polecam używanie coś znajdziesz w tej kwestii:

Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?

Zajmuje się przypadki, w których nie jest wymagane invoke, jest wywoływana z różnych wątków, rączka jest lub nie jest tworzony, etcetcetc. Można go łatwo zmodyfikować tak, aby był SafeInvoke() i SafeBeginInvoke(), jeśli nie jesteś fanem parametru bool.

(wliczony tutaj dla wygody:

/// Usage: 
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false); 

// or 
string taskName = string.Empty; 
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true); 


/// <summary> 
/// Execute a method on the control's owning thread. 
/// </summary> 
/// <param name="uiElement">The control that is being updated.</param> 
/// <param name="updater">The method that updates uiElement.</param> 
/// <param name="forceSynchronous">True to force synchronous execution of 
/// updater. False to allow asynchronous execution if the call is marshalled 
/// from a non-GUI thread. If the method is called on the GUI thread, 
/// execution is always synchronous.</param> 
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous) 
{ 
    if (uiElement == null) 
    { 
     throw new ArgumentNullException("uiElement"); 
    } 

    if (uiElement.InvokeRequired) 
    { 
     if (forceSynchronous) 
     { 
      uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); 
     } 
     else 
     { 
      uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); 
     } 
    } 
    else 
    { 
     if (!uiElement.IsHandleCreated) 
     { 
      // Do nothing if the handle isn't created already. The user's responsible 
      // for ensuring that the handle they give us exists. 
      return; 
     } 

     if (uiElement.IsDisposed) 
     { 
      throw new ObjectDisposedException("Control is already disposed."); 
     } 

     updater(); 
    } 
} 
0

prostu zorientowali się prostszy sposób bez używania Invoke:

int fakepercentage = -1; 
//some loop here......if no loop exists, just change the value to something else 
if (fakepercentage == -1) 
{ 
    fakepercentage = -2; 
} 
else 
{ 
    fakepercentage = -1; 
} 
backgroundworker1.ReportProgress(fakepercentage); 

Następnie w backgroundworker1_ProgressChanged (object sender, ProgressChangedEventArgs e):

if (e.ProgressPercentage < 0) 
{ 
    //access your ui control safely here 
} 
Powiązane problemy