Wątki - problem z niekończącym się wątkiem

0

Witam.
Mam dość dziwny problem dotyczący wątków. Napisałem program, którego jednym z elementów jest wczytanie zawartości pliku i wrzucenie go do JTextArea. Z uwagi na to, że wczytywanie binarne dla dużych plików czasem trwało dość długo zainteresowałem się tematem wątków - samo wczytywanie dałem do jednego wątku a w drugim podałem stworzenie okna z JProgressBarem, żeby pokazać, że program nie zawiesił się. Drugi wątek czekał na zakończenie pierwszego a następnie, jeśli pierwszy był już nieaktywny zamykał okno z paskiem postępu. Kod jest spory bo program robi jeszcze parę innych rzeczy ale podałem najważniejsze fragmenty:

package project;
/**
 * Project application
 *
 * @author Fazid
 * @version 1.10 Alfa 08/04/25
 */
import java.awt.*;
import java.awt.Image.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;


 
public class Main {
    public static void main (String[] args)
    {
        Window okno = new Window();
    }    
}

class Window extends JFrame
{
    public static String tekst;

    static JButton cancel;
    
    static JDialog bar;
    static CImport w1;
    static CWait w2;
    
    static JTextArea textArea;
    
    static JFileChooser fc;
    Window() {
     
        fc = new JFileChooser();

        /*---Budowanie okna głównego---*/
       
        tekst="";
    }
    class ButtonsL extends MouseAdapter
    {
	public void mousePressed (MouseEvent e)
	{
               JButton src = (JButton) e.getSource();
              
               /*Obsługa wszystkich przycisków w programie*/

               if (src==cancel)
               {
                   w1.interrupt();
                   bar.dispose();
               }
               
	}
    } 
    class CMenu implements ActionListener
    {
        public void actionPerformed (ActionEvent e)
        {
                /*---MENU_EVENT_SYSTEM---*/
                JMenuItem src = (JMenuItem) e.getSource();
                if (src==open)
                {
                    int returnVal = fc.showOpenDialog(Window.this);
                    if (returnVal==fc.APPROVE_OPTION)
                    {
                        w1 = new CImport();
                        w2 = new CWait(w1);
                        w1.start();
                        w2.start();
                    }
                }
                /*---END_OF_MENU_EVENT_SYSTEM---*/
        }
    }
    class CImport extends Thread
    {
        public void run()
        {
             File plik=fc.getSelectedFile();
             tekst="";
             try
             {
                    FileInputStream fstr = new FileInputStream(plik);
                    InputStreamReader str = new InputStreamReader(fstr,"UTF-8");
                    BufferedReader bf = new BufferedReader(str);        
                    int znak = bf.read();
                    while( znak != -1 )
                    {
                         tekst += (char) znak;
                         znak = bf.read();
                    }
                    textArea.setText(tekst);
              }
              catch (FileNotFoundException ex)
              {
                    textArea.setText("Brak pliku");
              }
              catch (IOException ex)
              {
                     textArea.setText("Blad odczytu");
              }
        }
    }
    class CWait extends Thread
    {
        Thread w;
        CWait(Thread j)
        {
            w=j;
        }
        public void run()
        {
            bar= new JDialog();
            bar.setTitle("Czekaj...");
            bar.setLayout(null);
            bar.setSize(300,110);
            bar.setLocationRelativeTo(null);
            bar.setModal(true);	
            bar.addWindowListener(new Koniec());
                
            JProgressBar progress = new JProgressBar(0,100);
            progress.setBounds(30,20,240,20);
            progress.setIndeterminate(true);
            
            cancel = new JButton("Anuluj");
            cancel.setBounds(170,45,100,20);
            cancel.addMouseListener(new ButtonsL());
                
            bar.add(cancel);
            bar.add(progress);
            bar.setVisible(true);
            
            while (w.isAlive())
            {
                    
            }
            bar.dispose();    
        }
        class Koniec extends WindowAdapter
        {
            public void windowClosing (WindowEvent e)
            {
                e.getWindow().dispose();
                w1.interrupt();
                w2.interrupt();
            }
        }
    }
}

W działaniu wygląda to tak, że pojawia się okno wyboru pliku, po wybraniu pliku pojawia się okno z paskiem postępu jednak to okno nigdy nie znika. Ale dane są wczytane bo za jakiś czas pojawia się zaimportowany tekst. więc mimo zakończenia pracy przez wątek pierwszy ten drugi nie kończy pracy. Jeżeli zamknę okno z paskiem postępu używając krzyżyka dostaję informację w konsoli:

Disposal was interrupted:
java.lang.InterruptedException
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:485)
        at java.awt.EventQueue.invokeAndWait(EventQueue.java:992)
        at java.awt.Window.doDispose(Window.java:994)
        at java.awt.Dialog.doDispose(Dialog.java:1243)
        at java.awt.Window.dispose(Window.java:937)
        at project.Window$CWait.run(Main.java:324)

Wskazuje na linijkę bar.dispose();
Nie mam pojęcia dlaczego całość nie działa - będę wdzięczny za pomoc ;)

0

Jeśli masz takie poglądy

Poprawianie czegoś co działa dobrze sprawi, że zacznie to działać źle. Poprawianie czegoś co działa źle sprawi, że zacznie to działać jeszcze gorzej.

to nie oczekuj pomocy, dlaczego mamy pogarszać działanie twojego programu ? (na poprawę nie ma szans)
pozdrawiam

0

Skoro zrobiłeś to :

                w1.interrupt();
                w2.interrupt();

To nic dziwnego, że masz to :

java.lang.InterruptedException

Jeżeli chodzi o to, że pierwszy z wątków Ci się nie kończy, na początek dałbym klauzulę finally, w której pozamykałbym te wszystkie strumienie, które tworzysz (użyj metod close()).
Jak nie pomoże, napisz, wtedy może dokładniej obejrzę Twój kod.

0
bogdans napisał(a)

Jeśli masz takie poglądy

Poprawianie czegoś co działa dobrze sprawi, że zacznie to działać źle. Poprawianie czegoś co działa źle sprawi, że zacznie to działać jeszcze gorzej.

to nie oczekuj pomocy, dlaczego mamy pogarszać działanie twojego programu ? (na poprawę nie ma szans)
pozdrawiam

Prawo Murphy'ego. Odnosi się tylko i wyłącznie do właściciela- tj. do mnie. Więc nie dotyczy to Ciebie bądź innych, którzy będą w stanie mi pomóc ;)

@kamikadze
Niestety, zamknięcie strumieni nie pomogło - dalej pierwszy wątek się nie kończy :/

0
  while (w.isAlive())
            {
                   
            }

Bój się Boga za pisanie takich rzeczy.

Do wieszania wątków używaj wait() + notify() lub semaforów.

Poza tym podaj cały kod.
Bo to co podałeś robi tylko: Window okno = new Window(); fc = new JFileChooser(); tekst="";

0

OK, spróbowałem metod wait() i notify() ale poza rzuconym wyjątkiem IllegalMonitorStateException podczas wykonywania tych wątków nic nie zyskałem ;)

Cały kod: http://wklej.org/id/17171544dd
Przepisany już na powyższe metody jednak dalej nie działa jak powinno.

0

wait() i notify() muszą być wewnątrz metod z przedrostkiem synchronized

0

Pobawiłem się nieco debuggerem i doszedłem do ciekawych obserwacji. Okienko z paskiem postępu nigdy mi się nie zamyka, bo program nigdy nie dochodzi do momentu sprawdzania aktywności drugiego wątku. Zatrzymuje się na linijce bar.setVisible(true) i nic dalej się nie dzieje. Więc jakkolwiek bym nie sprawdzał aktywności pierwszego wątku (czy pierwotnie przez w.isAlive() czy przez wait() i notify()) nie ma możliwości, żebym okno zamknął. Wszystko umieszczone poniżej linijki bar.setVisible(true) nie wykonuje się, chociażby nawet była to jakaś prosta instrukcja typu wyświetlania informacji.

Usunięcie tej linijki powoduje, że program działa dobrze, tylko bez sensu, bo okno się w ogóle nie pojawia.

Udało mi się problem rozwiązać zmieniając typ okna bar z JDialog na JFrame i działa - bez względu na sprawdzanie aktywności wątku. Wie ktoś może dlaczego coś takiego miało miejsce? Tak na przyszłość miłoby było wiedzieć ;) Poza tym JDialog byłby nieco lepszy dla mnie ze względu na możliwość ustawienia modalności, czego na JFrame zrobić nie mogę.

I jeszcze jedno - w oknie z paskiem postępu mam buttona, który zamyka mi okno i zatrzymuje pobieranie danych z pliku w pierwszym wątku przez instrukcje w1.interrupt(); Jednak wciśnięcie go nie powoduje wcale przerwania wątku - kod z pierwszego wątku dalej się wykonuje.. Jeżeli zamienię interrupt() na stop() wszystko działa tak jakbym tego oczekiwał. Jednak w dokumentacji Javy można znaleźć zapis, że metoda stop() została wycofana ze względów bezpieczeństwa. Więc jak to powinienem rozwiązać?

0

Zrób może w wątku, który chcesz przerywać pętlę

  private boolean done=false;
  ...
  public void run()
  {
    done=false
    while(!done)
    {
      ...
    }
  }
  public void setDone(boolean done)
  {
     this.done=done;
  }

może to nie pogorszy
pozdrawiam

0

...

0
szuk napisał(a)

Z wątkami trzeba trochę poeksperymentować.

Na to wygląda... W moim przypadku jest tym gorzej, że w wątku, który chcę przerwać mam praktycznie same operacje I/O co jak wynika z informacji wygrzebanych z Internetu może sprawiać problemy przy przerwaniu. Wtedy interrupt() ustawia tylko flagę przerwania ale wątek może być kontynuowany. Więc napisałem cobie coś takiego:

class CImport extends Thread
    {
        public void run()
        {
             File plik=fc.getSelectedFile();
             FileInputStream fstr=null;
             InputStreamReader str=null;
             BufferedReader bf=null;
             try
             {
                    fstr = new FileInputStream(plik);
                    str = new InputStreamReader(fstr,"windows-1250");
                    bf = new BufferedReader(str);        
                    String linia = bf.readLine();
                    while(linia != null )
                    {
                         tekst += linia;
                         tekst +="\n";
                         linia = bf.readLine();
                         if (this.isInterrupted()) linia=null; //przerwanie odczytu jeżeli anulowano operację
                    }
                    fstr.close(); 
                    str.close();
                    bf.close();
              }
              catch (FileNotFoundException ex)
              {
                    textArea.setText("Brak pliku");
              }
              catch (IOException ex)
              {
                     textArea.setText("Blad odczytu");
              }
              if (!this.isInterrupted())
              {
                  textArea.setText(tekst);
              }
        }
    }

W pętli sprawdzam, czy flaga zakończenia nie jest ustawiona, jeśli jest to kończę pętlę i nie wstawiam pobranego z pliku tekstu. I działa ;) Ale bez Waszych wskazówek pewnie do tego bym nie doszedł ;)

Jeszcze zostaje jedna kwestia - dlaczego mój program nie działał na JDialog? Obszerniej o tym piszę w poprzednim poście. Dziwny przypadek i z chęcią dowiedziałbym się dlaczego tak się zachowuje.

0

Strzał na ślepo, ale spróbuj JDialog skonstruować dając mu jako właściciela (owner) twoją ramkę główną i obadaj, czy efekt będzie taki sam ...

0

...

0

Jesli chcesz poprawnie wyswietlic pasek postepu, ktory reprezentuje jakies obliczenia MUSISZ obliczenia umiescic w innym watku niz tworzone okienko z paskiem postepu.
Dodatkowo w watku powinno sie uzywac

while(!interrupted) a nie while(true)

poza tym poczytaj jaka jest roznica pomiedzy interrupted() a isInterrupted(); oraz kiedy ustawiana jest flaga interrupted a kiedy rzucany wyjatek - po czym latwo dojdziesz do wniosku iz w klauzuli catch musisz wywolac interrupt;

catch(InterruptedException){
this.interrupt();
}

pozdrawiam

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