Problem z wielowątkowością w aplikacji okienkowej

0

Aplikacja na samym początku działania odpala wątek, który pracuje do końca jej działania. po Jakimś czasie użytkownikowi wyświetlane jest nowe okno, na którym później wątek z pierwszego okienka wykonuje przeróżne operacje.
Drugie okno uruchamiam w następujący sposób.

 
window = new Form1();
windowThread = new Thread(() => window.ShowDialog());
windowThread.Start();

Próbowałem innych sposobów ale nie wszystko działało dobrze, gdzieś znalazłem aby uruchomić w powyższy sposób i działa.

W póżniejszym stadium aplikacja dodaje do drugiego okienka kontrolke DataGridView - i tutaj występuje mój problem. Gdy używam skrótu klawiszowego Ctrl+C w DataGridView apliakcja wywala nastepujący wyjątek w metodzie ShowDialog():

 
ThreadStateException 
Bieżący wątek musi być ustawiony na tryb jednowątkowego apartamentu, aby można było wykonywać wywołania OLE. Upewnij się, że w funkcji Main jest zaznaczony element STAThreadAttribute.

próby które podjąłem do tej pory w celu wyeliminowania wyjątku:

  • zmieniać STAThreadAttribute na MTAThreadAttribute i odwrotnie - nic nie dało
  • nadpisac akcje kliknięcia jakiegokolwiek klawisza w gridView - nic nie dało
  • wyłapać wyjątek - okno aplikacji się zamyka - chciałbym tego uniknąć.

jakieś pomysły jak to ominąć / co robię źle? / jak taką sytuację rozwiązać?

z góry dziękuję za wszelkie uwagi.

1

Generalnie, tworzenie UI w jednej aplikacji na kilku wątkach.. potrafi być problematyczne i w gruncie rzeczy powoduje tylko problemy. Plusów zbyt wielu nie widzę.
Jeden wątek UI spokojnie sobie poradzi z wieloma oknami, kontrolkami, przesyłaniem danych pomiędzy nimi - będzie prościej. W przypadku wielowątkowego UI musisz pamiętać, że nie możesz tak po prostu korzystać z obiektów z jednego wątku w drugim. W twoim kodzie chociażby tworzysz formę w jednym, a wyświetlasz w drugim wątku.

Niech cały interfejs chodzi na jednym wątku, a na pozostałe zrzucaj same czasochłonne operacje. I gdy już w trakcie jej wykonywania będziesz musiał uaktualnić interfejs to pamiętaj o Invoke / InvokeRequired (wygugluj).

0
Rev napisał(a)

Generalnie, tworzenie UI w jednej aplikacji na kilku wątkach.. potrafi być problematyczne i w gruncie rzeczy powoduje tylko problemy. Plusów zbyt wielu nie widzę.
Jeden wątek UI spokojnie sobie poradzi z wieloma oknami, kontrolkami, przesyłaniem danych pomiędzy nimi - będzie prościej. W przypadku wielowątkowego UI musisz pamiętać, że nie możesz tak po prostu korzystać z obiektów z jednego wątku w drugim. W twoim kodzie chociażby tworzysz formę w jednym, a wyświetlasz w drugim wątku.

Niech cały interfejs chodzi na jednym wątku, a na pozostałe zrzucaj same czasochłonne operacje. I gdy już w trakcie jej wykonywania będziesz musiał uaktualnić interfejs to pamiętaj o Invoke / InvokeRequired (wygugluj).

Na samym początku sam interfejs działał na jednym wątku, ale występowały dziwne problemy przy wywoływaniu Invoke - mianowicie na samym początku aplikacja potrzebuje trochę czasu na pobranie informacji z servera - uruchamiam wtedy pasek postępu i chowam niektóre kontrolki. Po pobraniu wszystkich niezbędnych informacji pokazuję użytkownikowi te kontrolki ( wywołanie show z wątku nasłuchującego serwer ) niestety kontrolek tych jest wiele i wszystkie operacje show muszę wywoływać poprzez invoke. Problem polegał na tym, że jeżeli miałem dwie kontrolki - przykładowo przycisk i panel, chcę pokazać obie, obie muszę pokazać przez Invoke i teraz aplikacja wykonywała pierwszy invoke a następnego w dziwny sposób nie( po prostu wracała do dalszego działania z widocznym przyciskiem i niewidocznym panelem), nie moglem rozkminić o co chodzi, a otworzenie drugiego okna w osobnym wątku rozwiązało ten problem.

0

Dziwne tłumaczenie, raczej coś zwaliłeś. Poza tym nie trzeba każdej pojedynczej operacji wykonywać przez osobne invoke. Jeśli panel i button były powiązane, to wykonujesz przez invoke odpalenią metodę PokazPanelIButton(), w której robisz widoczne obie kontrolki. Zrozumiałeś idee?
Jeśli modyfikujesz sporo elementów gui warto zawiesić przetwarzanie gui przez suspendLayout, uchroni cię to przez zbyt dużą liczbą odświeżeń.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.suspendlayout.aspx

0
massther napisał(a)

Dziwne tłumaczenie, raczej coś zwaliłeś. Poza tym nie trzeba każdej pojedynczej operacji wykonywać przez osobne invoke. Jeśli panel i button były powiązane, to wykonujesz przez invoke odpalenią metodę PokazPanelIButton(), w której robisz widoczne obie kontrolki. Zrozumiałeś idee?
Jeśli modyfikujesz sporo elementów gui warto zawiesić przetwarzanie gui przez suspendLayout, uchroni cię to przez zbyt dużą liczbą odświeżeń.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.suspendlayout.aspx

Nie twierdzę, że nic nie zwaliłem- wręcz przeciwnie raczej na pewno coś zwaliłem i dlatego proszę o pomoc. Wydaje mi się, że chyba nie do końca zrozumiałeś mój problem. - możliwe że ja źle to ująłem, więc spróbuję raz jeszcze.
Otóż:
mam dwie Formy Form1 i Form2:
Form1 odpala wątek do komunikacji z serwerem - wczytuje różne informacje, następnie tworzy Form2 i ustawia niektóre elementy oraz niektóre chowa. Metoda do tego wygląda mniej więcej tak:

 
public void hideControls()
        {

            if (this.menuStrip1.InvokeRequired)
            {
                this.menuStrip1.Invoke((MethodInvoker)delegate
                {
                    this.menuStrip1.Enabled = false;
                });
            }
            else
            {
                this.menuStrip1.Enabled = false;
            }

            if (panel1.InvokeRequired)
            {
                panel1.Invoke((MethodInvoker)delegate
                {
                    panel1.Hide();
                });
            }
            else
            {
                panel1.Hide();
            }
}

z tym, że tych kontrolek do ukrycia jest troszeczkę więcej + pokazywany jest pasek postępu, w międzyczasie dalej są wczytywane dane z serwera, po wczytaniu wszystkich danych znowu wykonywane są pewne operacje na Form2 i używana metoda ShowControls, która działa analogicznie do hide, z tym. takie jest założenie - w praktyce wychodzi na to że po wywołaniu metody HideCOntrols z wątku komunikującego się z serwerem wątek ten nagle przestaje działać. Przykładowo:

 

        private void watekJakBymChcialAbyDzialal()
        {
            bool bActive = true;
            while (bActive)
            {
                //komunikacja z serwerem
                //...
                HideControls();
                //...
                //dalsza komunikacja
                ShowControls();
            }
        }

        private void watekJakDziala()
        {
            bool bActive = true;
            while (bActive)
            {
                //komunikacja z serwerem
                //...
                HideControls();
                return;
                //...
                //dalsza komunikacja
                ShowControls();
            }
        }

z tym, że tego return'a tam oczywiście nie ma :P
I teraz tak działa ten wątek jeśli odpalam Form2 przez ShowDialog nie jako wątek. Jeśli odpalam Przez Show to Form2 zwiesza się (brak odpowiedzi) i nic nie mogę zrobić.
Jeśli odpalam Form2 jako nowy wątek to wszystko działa jak trzeba - oprócz tych skrótów klawiszowych o których wspomniałem w pierwszym poście.

Mam nadzieję, że teraz opisałem dość przejrzyście mój problem i jeśli zrobiłem jakiś błąd w implementacji to z łatwością będzie można mi pomóc.
Z góry bardzo dziękuję za wszelką pomoc.

0

No właśnie chyba zrozumiałem że robisz źle :)
Na początek hideControls, dlaczego każdą kontrolkę przez osobne wywołanie Invoke ukrywasz, jak pisałem lepiej za pomocą jednego.

void showHideControls(bool hide) // w klasie form
{
  if (this.InvokeRequired)
  {
    this.Invoke(new Action<bool>(showHideControls), new object[] {hide});
  }
  else
  {
    if (hide)
    {
      ctrl1.Hide();
      this.menuStrip1.Enabled = false;
      ...
    }
    else
    {
      ctrl1.Show();
      this.menuStrip1.Enabled = true;
      ...
    }
  }
}

Dlaczego to form1 tworzy wątek, a form2 do dalej używa? Dlaczego najpierw coś wczytujesz, ukrywasz kontrolki, znowu wczytujesz i pokazujesz? Wydaje mi się że trochę koncepcyjnie źle to wymyśliłeś.
Nie możesz tak:
Forma1 tworzy Form2.
Form2 na ukryte kontrolki i pokazany tylko pasek postępu.
Forma2 w Load uruchamia wątek roboczy, do komunikacji z serwerem.
Wątek roboczy jeśli coś wczyta modyfikuje gui, czyli ustawia takieś labelki, textboxy, combo, etc.
Dopiero po załadowaniu całości ukrywasz pasek postępu i pokazujesz wszystkie kontrolki.
Masz jeszcze następujące możliwości komunikacji między wątkami. Albo wątek roboczy jak coś przeczyta, to przez invoke modyfikuje gui, albo rzuca jakiś event że coś wczytał, a gui chwyta event (w obsłudze eventa też użyć invoke musisz, bo zostanie on rzucony z innego wątku niż gui, w którym ma być obsłużony).

Żeby powiedzieć czemu ci się wiesza, musiałbyś pokazać jak uruchamiasz wątek i jak z serwerem się komunikujesz.

1 użytkowników online, w tym zalogowanych: 0, gości: 1