2009-09-14 9 views
10

Mam funkcjiTemat Control.Invoke

public void ShowAllFly() 
{ 
     cbFly.Items.Clear(); 
     cbFly.Items.Add("Uçuş Seçiniz..."); 

     dsFlyTableAdapters.tblFlyTableAdapter _t=new KTHY.dsFlyTableAdapters.tblFlyTableAdapter(); 
     dsFly _mds = new dsFly(); 
     _mds.EnforceConstraints = false; 
     dsFly.tblFlyDataTable _m = _mds.tblFly; 
     _t.Fill(_m); 
     foreach (DataRow _row in _m.Rows) 
     { 
      cbFly.Items.Add(_row["FlyID"].ToString()+"-"+_row["FlyName"].ToString() + "-" + _row["FlyDirection"].ToString() + "-" + _row["FlyDateTime"].ToString()); 
     } 
     _Thread.Abort(); 
     timer1.Enabled = false; 
     WaitPanel.Visible = false; 
} 

W Form_Load działać jak ten;

{ 
    _Thread = new System.Threading.Thread(new System.Threading.ThreadStart(ShowAllFly)); 
    _Thread.Start(); 
    _Thread.Priority = System.Threading.ThreadPriority.Normal; 
} 

Ale kiedy go uruchomię;

w funkcji ShowAllFly

cbFly.Items.Clear(); ---- HERE Gives ERROR LIKE Control.Invoke must be used to interact with controls created on a separate thread. 

Na czym polega problem?

Odpowiedz

48

w Windows Forms Istnieją dwa złote zasady gwintowania:

  • Don” t dotykać jakichkolwiek właściwości lub metod sterowania (innych niż te jawnie wymienione jako w porządku) z dowolnego wątku innego niż ten, który utworzył "uchwyt" kontrolki (zwykle jest tylko jeden wątek UI)
  • Nie blokuj wątku UI dla każdego dłuższy czas, lub będziesz złożenia wniosku nie odpowiada

W celu interakcji z UI z innego wątku, trzeba „Marshall” połączenia do wątku UI, używając delegata i wywołując Control.Invoke/BeginInvoke. Możesz może sprawdzić, czy musisz zadzwonić pod numer Invoke za pomocą właściwości InvokeRequired, ale w dzisiejszych czasach ja osobiście zwykle po prostu to zrobić - nie ma zbyt wiele kar za wywoływanie, gdy nie trzeba.

Wyrażenia Lambda w C# 3 (lub metody anonimowe w C# 2) sprawiają, że jest to o wiele przyjemniejsze.

Na przykład, można użyć:

cbFly.Invoke((MethodInvoker)(() => cbFly.Items.Clear())); 

Wszystkie wsporniki się w sposób nieco, więc warto dodać metodę rozszerzenia w ten sposób, jeśli używasz C# 3:

public static void Invoke(this Control control, MethodInvoker action) 
{ 
    control.Invoke(action); 
} 

Następnie można zrobić:

cbFly.Invoke(() => cbFly.Items.Clear()); 

która jest sporo prostsze. Zwykle można uciec się przy użyciu MethodInvoker, przechwytując wszelkie zmienne potrzebne do uzyskania dostępu w ramach delegata.

Aby uzyskać więcej informacji, zobacz numer my threading tutorial lub Joe Albahari's.

Jako drugorzędna sprawa widzę, że używasz Thread.Abort - w rzeczywistości we własnym wątku, mimo że ma inne połączenia po nim. Czemu? Przerwanie dowolnego wątku, innego niż własny, jest tylko wywołaniem typu "tylko w sytuacjach awaryjnych" (zwykle powinno to nastąpić po rozładowaniu aplikacji) i nie widzę powodu, aby przerwać bieżący wątek, gdy nadal jest praca do wykonania potem...

6

Interakcje w sprawie kontroli w innym (UI) nici muszą być wywołana tak:

public delegate void ProcessResultDelegate(string result); 
void ProcessResult(string result) 
{ 
    if (textBox1.InvokeRequired) 
    { 
     var d = new ProcessResultDelegate(ProcessResult); 
     d.Invoke(result); 
    } 
    else 
    { 
     textBox1.Text = result; 
    } 
} 
+3

Dobre rozwiązanie; bym poleciłbym użyć 'Action ' ze struktury, zamiast toczyć (i utrzymywać) swój własny. –

+0

Tak, to normalny sposób na zrobienie tego. Powodem tego jest to, że textBox1.Text można zmienić tylko w wątku, w którym został utworzony textbox - wywołanie to proces używany do powrotu do tego wątku. –

+0

@Fredrik: Masz rację, ale ten przykład jest częściej widoczny, głównie dlatego, że był dłuższy. Każda metoda zadziała. –

3

Zawsze uważałem, że this article jest pomocny w tym konkretnym przypadku.

W tym przykładzie próbujesz zmodyfikować różne formanty z wątku, który nie utworzył formantu. Aby obejść ten problem danego Twój przykład, zamiast zrobić to (zakładając, że metoda ShowAllFly() to metoda na formularzu):

public void ShowAllFly() 
{ 
    Invoke((MethodsInvoker) delegate { 
     cbFly.Items.Clear(); 
     cbFly.Items.Add("Uçuş Seçiniz..."); 
     dsFlyTableAdapters.tblFlyTableAdapter _t = 
      new KTHY.dsFlyTableAdapters.tblFlyTableAdapter(); 
     dsFly _mds = new dsFly(); 
     _mds.EnforceConstraints = false; 
     dsFly.tblFlyDataTable _m = _mds.tblFly; 
     _t.Fill(_m); 
     foreach (DataRow _row in _m.Rows) 
     { 
      cbFly.Items.Add(_row["FlyID"].ToString() + "-" + 
          _row["FlyName"].ToString() + "-" + 
          _row["FlyDirection"].ToString() + "-" + 
          _row["FlyDateTime"].ToString()); 
     } 
     //_Thread.Abort(); // WHY ARE YOU TRYING TO DO THIS? 
     timer1.Enabled = false; 
     WaitPanel.Visible = false; 
    }); 
} 

Wystarczy podkreślić punkt @Jon Skeet wykonany, mam wykomentowane wezwanie do przerwania wątku. Wątek zakończy się samoczynnie. Nie ma powodu, aby go przerwać w ten sposób.

+1

Twój jest najlepszy Matt. Dziękuję Ci. Działa bardzo ładnie. – atromgame

+0

Ponieważ on jest najlepszy, dałem mu przegłos, który nikt przed mną nie miał (podpowiedź, podpowiedź). –

0

To musi być wywołanie ... Ale trzeba jeszcze poczekać na wątek główny, co oznacza, że ​​nie dostaniesz błędu w ten sposób, ale to nie działa dokładnie równolegle, jeśli chcesz przejść więcej niż jeden proces w tym samym czasie, po prostu stwórz więcej niż jeden wątek

Thread thread = new Thread(new delegate_method(method));//you must create delegate before 
thread.start(); 
Thread thread2 = new Thread(new delegate_method(method2));//you must create delegate before 
thread.start(); 

obsługi dwóch procesów w tym samym czasie

void method() 
{ 
//do something here -- working background Remember can not control any UI control from here 
finish_thread() 
} 

void method2() 
{ 
//do something here -- working background Remember can not control any UI control from here 
finish_thread() 
} 

void finish_thread() 
{ 
if(invoke.Required) 
{ 
//Here you have to call delegate method here with UI 
BeginInvoke(new delegate_method(finish_thread)); 
} 
else 
{ 
//Now you can control UI thread from here and also you finished background work 
//Do something working with UI thread 
textBox.Text = ""; 
} 
}