Jak zabić wątek w Javie?

374

Jak zabić java.lang.Threadw Javie?

Lecieć jak po sznurku
źródło
2
do tej pory nie możesz zabić nici; ponieważ
funkcja
1
Wolę odpowiedź dotyczącą ExecutorStatustego pytania: stackoverflow.com/questions/2275443/how-to-timeout-a-thread
Kirby,
9
@loungerdork „Myślę, że Java powinna wdrożyć bezpieczną metodę zatrzymania / zniszczenia dla niekontrolowanych wątków, nad którymi nie masz kontroli, pomimo zastrzeżeń związanych z utratą blokad i innymi pułapkami” Więc chcesz mieć niebezpieczne zatrzymanie wątku. Myślę, że już masz.
DJClayworth,
4
To zadziwiające, jakie pytania uzyskałyby w 2009 roku 212 głosów pozytywnych. Zostałoby to dziś natychmiast zniszczone.
Jonathon Reinhart,
7
@JonathonReinhart: Dlaczego tak jest? To wydaje się uzasadnione pytanie, nawet w dzisiejszych czasach. Może nie znasz frustracji, którą wywołuje, gdy masz niekontrolowane wątki i możesz użyć przestarzałych funkcji, aby jakoś sobie z tym poradzić?
TFuto,

Odpowiedzi:

189

Zobacz ten wątek firmy Sun, dlaczego są one przestarzałeThread.stop() . Szczegółowo wyjaśnia, dlaczego była to zła metoda i co należy zrobić, aby bezpiecznie zatrzymać wątki w ogóle.

Zalecają, aby używać zmiennej wspólnej jako flagi, która prosi wątek w tle o zatrzymanie. Ta zmienna może być następnie ustawiona przez inny obiekt żądający zakończenia wątku.

JaredPar
źródło
1
jeśli sprawdzisz wątek, który przerwałeś IsAlive (), zwróci ci wartość true i będą kontynuować dodawanie do twojej bieżącej ThreadGroup [], możesz to zobaczyć za pomocą Thread.currentThread.getThreadGroup (). list (); wydrukuje wszystkie wątki, które ma i zobaczysz wiele wystąpień twojego wątku, jeśli powtórzysz swój przepływ.
AZ_
3
Jeśli jesteś na komputerze, to nie ma problemu, ale jeśli tworzysz oprogramowanie dla telefonu komórkowego (Android, którego doświadczyłem), otrzymasz OutOfMemoryError
AZ_
2
Należy / należy zauważyć, że aby zapewnić szybką komunikację żądania zatrzymania za pośrednictwem flagi, zmienna musi być niestabilna (lub dostęp do zmiennej musi być zsynchronizowany), zgodnie z zaleceniem.
mtsz
2
Ten link został zabity w tym momencie. Udało mi się to jednak znaleźć na archive.org: web.archive.org/web/20090202093154/http
Jay Taylor
2
Używam metody getConnection()z java.sql.DriverManager. Jeśli próba połączenia trwa zbyt długo, próbuję zabić odpowiedni wątek, dzwoniąc, Thread.interrupt()ale nie ma to żadnego wpływu na wątek. Thread.stop()Działa jednak, choć Oracle twierdzi, że nie powinno działać, jeśli interrupt()nie robi. Zastanawiam się, jak to działa i unikaj stosowania przestarzałej metody.
Danny Lo
129

Generalnie nie ..

Poprosisz go, aby przerwał cokolwiek robi za pomocą Thread.interrupt () (link javadoc)

Dobre wyjaśnienie, dlaczego znajduje się w javadoc tutaj (link do java technote)

Fredrik
źródło
@Fredrik Co dzieje się z kontekstem wątku, gdy interrupt()wywoływana jest metoda? Główne pytanie dotyczy generowania dziennika dla każdego nowego wątku.
ABcDexter,
3
@ABcDexter Chodzi o to, że przerwanie niczego nie przerywa, tylko sygnalizuje kodowi w wątku (lub kodowi wywoływanemu przez wątek), że ktoś poprosił go o przerwanie tego, co robi. Wątek ma następnie ładnie zatrzymać przetwarzanie i zwrócić, tak jak gdyby zrobił to, co powinien (w tym momencie kontekst wątku jest prawdopodobnie również odrzucany). OTOH, gdybyś naprawdę zmusił do zatrzymania wątku, twoje pytanie byłoby naprawdę dobre, a odpowiedź nieokreślona.
Fredrik
64

W Javie wątki nie są zabijane, ale zatrzymanie wątku odbywa się w trybie współpracy . Wątek jest proszony o zakończenie, a następnie można go bezpiecznie zamknąć.

Często volatile booleanużywane jest pole, które wątek okresowo sprawdza i kończy, gdy zostanie ustawione na odpowiednią wartość.

Nie użyłbym a, booleanaby sprawdzić, czy wątek powinien się zakończyć . Jeśli użyjesz go volatilejako modyfikatora pola, będzie to działało niezawodnie, ale jeśli kod stanie się bardziej złożony, ponieważ zamiast tego używa innych metod blokowania w whilepętli, może się zdarzyć, że Twój kod w ogóle się nie zakończy lub przynajmniej zajmie więcej czasu, ponieważ może chcieć.

Niektóre metody bibliotek blokujących obsługują przerwanie.

Każdy wątek ma już status przerwania flagi logicznej i powinieneś z niego skorzystać. Można to zaimplementować w następujący sposób:

public void run() {
   try {
      while (!interrupted()) {
         // ...
      }
   } catch (InterruptedException consumed)
      /* Allow thread to exit */
   }
}

public void cancel() { interrupt(); }

Kod źródłowy zaadaptowany z Java Concurrency in Practice . Ponieważ cancel()metoda jest publiczna, możesz pozwolić innemu wątkowi wywoływać tę metodę, jak chcesz.

Konrad Reiche
źródło
A co zrobić, jeśli uruchamiasz niezaufany kod jako wtyczkę lub skrypt? Java ma osadzoną piaskownicę dla niezaufanego kodu. A ta piaskownica jest bezużyteczna, pozwala na pracę bez silnego zatrzymania. Wyobraź sobie, że piszesz przeglądarkę na java. Możliwość zabicia dowolnego skryptu strony jest bezcenna.
ayvango,
@ayvango Następnie musisz uruchomić ten skrypt we własnej piaskownicy. Piaskownica Java chroni maszynę przed aplikacją, a nie przed jej częściami.
David Schwartz,
@DavidSchwartz masz na myśli, że powinienem użyć innej platformy niż Java?
ayvango,
@ayvango Nadal możesz używać piaskownicy Java, aby chronić maszynę przed aplikacją. Ale jeśli chcesz chronić części aplikacji przed innymi częściami aplikacji, musisz wybrać narzędzie, które może to zrobić.
David Schwartz,
@DavidSchwartz ASFAIK takie narzędzia nie mogłyby istnieć, jeśli nie są obsługiwane na poziomie platformy. Oczywiście zadanie to można rozwiązać za pomocą w pełni zinterpretowanego silnika skryptowego. Może liczyć redukcje jak robi Erlang i robić inne rzeczy.
ayvango,
20

Jednym ze sposobów jest ustawienie zmiennej klasy i użycie jej jako wartownika.

Class Outer {
    public static volatile flag = true;

    Outer() {
        new Test().start();
    }
    class Test extends Thread {

        public void run() {
            while (Outer.flag) {
                //do stuff here
            }
        }
    }

}

Ustaw zmienną klasy zewnętrznej, tj. Flag = true w powyższym przykładzie. Ustaw wartość false, aby „zabić” wątek.

karim79
źródło
2
Podobnie jak podpowiedź boczna: Zmienna as flag działa tylko wtedy, gdy wątek działa i nie jest zablokowany. Thread.interrupt () powinien zwolnić wątek z większości warunków oczekiwania (czekanie, uśpienie, odczyt sieci itp.). Dlatego nigdy nie powinieneś nigdy wychwytywać InterruptedException, aby to zadziałało.
ReneS
12
To nie jest wiarygodne; zrób „flagę”, volatileaby upewnić się, że działa poprawnie wszędzie. Klasa wewnętrzna nie jest statyczna, więc flaga powinna być zmienną instancji. Flaga powinna zostać wyczyszczona metodą akcesora, aby można było wykonać inne operacje (takie jak przerwanie). Nazwa „flaga” nie ma charakteru opisowego.
erickson
2
Nie dostaję metody „while” w metodzie run. Czy to nie oznacza, że ​​wszystko, co jest zapisane w metodzie uruchamiania, będzie powtarzane? nie jest to coś, co chcieliśmy zrobić od wątku :(
2
+1 wykonanie podczas gdy (! Thread.currentThread (). IsInteruppted ()) jest preferowane
Toby
2
Oba przypadki zawodzą, gdy np. Otworzysz proces zewnętrzny w czasie while {// open ext process} i proces ten zostanie zawieszony, teraz ani wątek nie zostanie przerwany, ani nie osiągnie końca, aby sprawdzić stan logiczny, a ty jesteś pozostało zawieszone ... wypróbuj to, np. uruchom konsolę python za pomocą java.exec i spróbuj odzyskać kontrolę bez pisania polecenia exit i sprawdź, czy istnieje sposób na zabicie tego procesu i wyjście ... nie ma możliwości wyjdź z takiej sytuacji ...
Space Rocker
11

Jest sposób, jak to zrobić. Ale jeśli musiałeś go użyć, albo jesteś złym programistą, albo używasz kodu napisanego przez złych programistów. Powinieneś więc pomyśleć o tym, aby przestać być złym programistą lub przestać używać tego złego kodu. To rozwiązanie dotyczy tylko sytuacji, gdy NIE MA INNEGO SPOSOBU.

Thread f = <A thread to be stopped>
Method m = Thread.class.getDeclaredMethod( "stop0" , new Class[]{Object.class} );
m.setAccessible( true );
m.invoke( f , new ThreadDeath() );
VadimPlatonov
źródło
2
Nie ma w ogóle żadnych powodów, aby to robić, ponieważ nadal można dzwonić do publiczności, Thread.stopnawet jeśli jest przestarzała.
Lii,
1
@Lii Thread.stoprobi to samo, ale sprawdza także dostęp i uprawnienia. Używanie Thread.stopjest raczej oczywiste i nie pamiętam powodu, dla którego go użyłem Thread.stop0. Może Thread.stopnie działało w moim specjalnym przypadku (Weblogic na Javie 6). A może dlatego, że Thread.stopjest przestarzały i powoduje ostrzeżenie.
VadimPlatonov,
1
W moim scenariuszu był to jedyny sposób na zatrzymanie niekończącego się wątku. Z jakiegoś powodu .stop () nie zatrzymał wątku, ale stop0 () zrobił
fiffy
10

Chcę dodać kilka obserwacji na podstawie zebranych komentarzy.

  1. Thread.stop() zatrzyma wątek, jeśli menedżer bezpieczeństwa na to zezwoli.
  2. Thread.stop()jest niebezpieczny. Powiedziawszy to, jeśli pracujesz w środowisku JEE i nie masz kontroli nad wywoływanym kodem, może być konieczne; zobacz Dlaczego Thread.stop jest przestarzałe?
  3. Nigdy nie powinieneś zatrzymywać zatrzymywania wątku roboczego kontenera. Jeśli chcesz uruchomić kod, który zwykle się zawiesza, (ostrożnie) uruchom nowy wątek demona i monitoruj go, w razie potrzeby zabijając.
  4. stop()tworzy nowy ThreadDeathErrorbłąd w wątku wywołującym, a następnie zgłasza ten błąd w wątku docelowym . Dlatego ślad stosu jest generalnie bezwartościowy.
  5. W JRE 6 stop()sprawdza u menedżera bezpieczeństwa, a następnie wywołuje stop1()te połączenia stop0(). stop0()jest rodzimym kodem.
  6. Począwszy od wersji Java 13 ( Thread.stop()nie została jeszcze usunięta), ale Thread.stop(Throwable)została usunięta w wersji Java 11. ( lista mailingowa , JDK-8204243 )
Steve11235
źródło
8

Głosowałbym na Thread.stop().

Na przykład masz długotrwałą operację (jak żądanie sieciowe). Podobno czekasz na odpowiedź, ale może to zająć trochę czasu, a użytkownik przejdzie do innego interfejsu użytkownika. Ten oczekujący wątek jest teraz a) bezużyteczny b) potencjalny problem, ponieważ kiedy uzyska wynik, jest całkowicie bezużyteczny i wywoła wywołania zwrotne, które mogą prowadzić do wielu błędów.

Wszystko to, a on może przetwarzać odpowiedzi, które mogą wymagać dużej mocy obliczeniowej. A Ty, jako programista, nie możesz nawet tego zatrzymać, ponieważ nie możesz rzucać if (Thread.currentThread().isInterrupted())wierszy w całym kodzie.

Więc niemożność silnego zatrzymania nici jest dziwna.

Anfet
źródło
Jeśli operacja sieci jest już bezpiecznie przerwana, po prostu przerwij wątek. Jeśli działanie sieci nie jest bezpiecznie przerywane, i tak nie można Thread.stop()bezpiecznie dzwonić . Nie głosujesz za Thread.stop(), prosisz o każdą osobę, która realizuje każdą operację, która może zająć dużo czasu, aby bezpiecznie ją przerwać. I może to być dobry pomysł, ale nie ma to nic wspólnego z wdrażaniem Thread.stop()jako sposobu żądania bezpiecznej aborcji. Już to mamy interrupt.
David Schwartz
„Więc niemożność silnego powstrzymania dziwnego wątku”. - ... dopóki nie spojrzysz głęboko (tak jak zrobili to projektanci Java) i stwierdzisz, że nie ma technicznie wykonalnych rozwiązań, które nie byłyby gorsze niż deprecjacja stop.
Stephen C
5

Pytanie jest raczej niejasne. Jeśli miałeś na myśli „jak napisać program, aby wątek przestał działać, kiedy tego chcę”, wówczas różne inne odpowiedzi powinny być pomocne. Ale jeśli miałeś na myśli „Mam awarię z serwerem, nie mogę teraz zrestartować się i potrzebuję tylko konkretnego wątku, aby umrzeć, chodź co może”, to potrzebujesz narzędzia interwencyjnego, aby dopasować takie narzędzia monitorowania jstack.

W tym celu stworzyłem jkillthread . Zobacz instrukcję użytkowania.

Jesse Glick
źródło
Dziękuję Ci! Dokładnie tego szukałem!
Denis Kokorin
4

Oczywiście zdarza się, że uruchamiasz jakiś niezupełnie zaufany kod. (Mam to osobiście, umożliwiając wykonywanie przesłanych skryptów w moim środowisku Java. Tak, wszędzie dzwoni alarm bezpieczeństwa, ale jest to część aplikacji.) W tym niefortunnym przypadku po prostu masz nadzieję, pytając pisarzy skryptów szanować jakiś rodzaj logicznego sygnału uruchomienia / nie uruchomienia. Jedynym przyzwoitym zabezpieczeniem przed awarią jest wywołanie metody stop w wątku, jeśli, powiedzmy, działa on dłużej niż pewien limit czasu.

Ale jest to po prostu „przyzwoite”, a nie bezwzględne, ponieważ kod może wychwycić błąd ThreadDeath (lub jakikolwiek wyjątek, który jawnie zgłaszasz) i nie wyrzucać go tak, jak powinien to zrobić dżentelmen. Tak więc, podstawową kwestią jest AFAIA, nie ma absolutnie bezpiecznej awarii.

mlohbihler
źródło
AFAIK coraz więcej usług staje się hybrydowych i korzysta ze środowiska zarządzanego do wykonywania kodu innej firmy (wtyczki, skryptów itp.), Które nie mają pełnej kontroli nad kodem, całkowicie nierozsądne jest zdejmowanie wątku. ponieważ dla inżynierów serwisowych stan na żywo i służący może być nieskończenie lepszy niż stan nie służący (albo z powodu zawieszenia (który
usuwa
3

Nie ma sposobu, aby z wdziękiem zabić wątek.

Możesz spróbować przerwać wątek, jedną wspólną strategią jest użycie trującej pigułki, aby wysłać wiadomość do wątku, aby się zatrzymał

public class CancelSupport {
    public static class CommandExecutor implements Runnable {
            private BlockingQueue<String> queue;
            public static final String POISON_PILL  = stopnow”;
            public CommandExecutor(BlockingQueue<String> queue) {
                    this.queue=queue;
            }
            @Override
            public void run() {
                    boolean stop=false;
                    while(!stop) {
                            try {
                                    String command=queue.take();
                                    if(POISON_PILL.equals(command)) {
                                            stop=true;
                                    } else {
                                            // do command
                                            System.out.println(command);
                                    }
                            } catch (InterruptedException e) {
                                    stop=true;
                            }
                    }
                    System.out.println(“Stopping execution”);
            }

    }

}

BlockingQueue<String> queue=new LinkedBlockingQueue<String>();
Thread t=new Thread(new CommandExecutor(queue));
queue.put(“hello”);
queue.put(“world”);
t.start();
Thread.sleep(1000);
queue.put(“stopnow”);

http://anandsekar.github.io/cancel-support-for-threads/

Anand Rajasekar
źródło
2

Zasadniczo nie zabijasz, nie zatrzymujesz ani nie przerywasz wątku (ani nie sprawdzasz, czy jest przerwany ()), ale pozwalasz mu zakończyć się naturalnie.

To jest proste. Możesz użyć dowolnej pętli razem z (lotną) zmienną logiczną wewnątrz metody run () do kontrolowania aktywności wątku. Możesz także powrócić z aktywnego wątku do głównego wątku, aby go zatrzymać.

W ten sposób z wdziękiem zabijasz wątek :).

Wojciech Domalewski
źródło
1

Próby nagłego zakończenia wątku są dobrze znaną złą praktyką programistyczną i dowodem złego projektowania aplikacji. Wszystkie wątki w aplikacji wielowątkowej jawnie i niejawnie współużytkują ten sam stan procesu i są zmuszone do współpracy ze sobą, aby zachować spójność, w przeciwnym razie twoja aplikacja będzie podatna na błędy, które będą naprawdę trudne do zdiagnozowania. Dlatego obowiązkiem programisty jest zapewnienie takiej spójności poprzez staranne i przejrzyste projektowanie aplikacji.

Istnieją dwa główne właściwe rozwiązania kontrolowanych zakończeń wątków:

  • Zastosowanie wspólnej zmiennej zmiennej
  • Zastosowanie pary metod Thread.interrupt () i Thread.interrupt ().

Dobre i szczegółowe wyjaśnienie problemów związanych z nagłym zakończeniem wątków, a także przykłady niewłaściwych i właściwych rozwiązań kontrolowanego zakończenia wątków można znaleźć tutaj:

https://www.securecoding.cert.org/confluence/display/java/THI05-J.+Do+not+use+Thread.stop%28%29+to+terminate+threads

ZarathustrA
źródło
Ponieważ programy nie są już pisane przez jednego programistę, zabijanie wątków jest często konieczne. Dlatego nie można tego uznać za złe programowanie. Nie wyobrażam sobie Linuksa bez zabójstwa. Brak możliwości zabicia wątków jest wadą Javy.
Pavel Niedoba
1
Niesamowite, panie słuszne ... Co zrobić, jeśli trzeba zadzwonić do biblioteki innej firmy, nad którą nie ma się kontroli i która ma błędny limit czasu i może zawieszać się raz na 1000-2000 razy podczas wykonywania? Zła praktyka programowania, co? Cóż, nie zawsze masz dostęp do używanego kodu. W każdym razie operacja pyta, jak zabić wątek, a nie kontrolować jego przepływ przy projektowaniu własnego kodu ...
Arturas M
Pytanie brzmi, co się stanie, gdy zabijesz wątek, który był właścicielem muteksu lub ma przydzielony blok pamięci, lub który powinien wygenerować jakieś zdarzenie lub dane, na które czeka inny wątek? Co stanie się z resztą logiki aplikacji? Zawsze będziesz narażony na ryzyko, że mały i widoczny problem zostanie przekształcony w złożony problem, który będzie trudny do odtworzenia i zbadania. Zabicie wątku jest niebezpieczne, ponieważ może pozostawić twoją aplikację w wielu różnych niespójnych stanach. Spójrz na informacje z linku na stronie cert.org.
ZarathustrA
Mamy wątek kontrolny, który pod pewnymi warunkami może zdecydować, że całe obliczenia stały się bezcelowe. Wiele różnych programów uruchomieniowych wykonujących rzeczywistą pracę musi zostać spryskanych jakimś wariantem isInterrupted () w dowolnym dogodnym miejscu - jakie to okropne! O wiele ładniej jest, jeśli kontroler powoduje, że ThreadDeath wznosi się pozornie znikąd, czysto odwijając wszystkie zsynchronizowane i wreszcie bloki. Wszyscy mówią, że jest to podatne na błędy, ale w przypadku dość niezwiązanych wątków nie rozumiem, dlaczego.
Daniel
1

Nie udało mi się przerwać pracy w Androidzie, więc użyłem tej metody, działa idealnie:

boolean shouldCheckUpdates = true;

private void startupCheckForUpdatesEveryFewSeconds() {
    Thread t = new Thread(new CheckUpdates());
    t.start();
}

private class CheckUpdates implements Runnable{
    public void run() {
        while (shouldCheckUpdates){
            //Thread sleep 3 seconds
            System.out.println("Do your thing here");
        }
    }
}

 public void stop(){
        shouldCheckUpdates = false;
 }
Mindborg
źródło
2
należy dodać zmienne słowo kluczowe do shouldCheckUpdates, aby zoptymalizować kompilator z lokalną pamięcią wątków.
clinux
1

„Zabicie wątku” nie jest właściwym zwrotem. Oto jeden ze sposobów, w jaki możemy zaimplementować płynne zakończenie / wyjście z wątku:

Runnable, którego użyłem:

class TaskThread implements Runnable {

    boolean shouldStop;

    public TaskThread(boolean shouldStop) {
        this.shouldStop = shouldStop;
    }

    @Override
    public void run() {

        System.out.println("Thread has started");

        while (!shouldStop) {
            // do something
        }

        System.out.println("Thread has ended");

    }

    public void stop() {
        shouldStop = true;
    }

}

Klasa wyzwalająca:

public class ThreadStop {

    public static void main(String[] args) {

        System.out.println("Start");

        // Start the thread
        TaskThread task = new TaskThread(false);
        Thread t = new Thread(task);
        t.start();

        // Stop the thread
        task.stop();

        System.out.println("End");

    }

}
Adesh
źródło
niestabilne słowo kluczowe należy dodać do zmiennej shouldStop, na wypadek, gdyby kompilator zoptymalizował się z lokalną pamięcią wątków.
Oleksii Kyslytsyn
0

Thread.stop jest przestarzałe, więc jak zatrzymać wątek w Javie?

Zawsze używaj metody przerwania i w przyszłości, aby poprosić o anulowanie

  1. Gdy zadanie odpowiada na sygnał przerwania, na przykład, metoda blokowania kolejki bierze.
Callable < String > callable = new Callable < String > () {
    @Override
    public String call() throws Exception {
        String result = "";
        try {
            //assume below take method is blocked as no work is produced.
            result = queue.take();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return result;
    }
};
Future future = executor.submit(callable);
try {
    String result = future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    logger.error("Thread timedout!");
    return "";
} finally {
    //this will call interrupt on queue which will abort the operation.
    //if it completes before time out, it has no side effects
    future.cancel(true);
}
  1. Gdy zadanie nie reaguje na sygnał przerwania. Załóżmy, że wykonuje ono operacje we / wy gniazda, które nie reagują na sygnał przerwania, a zatem użycie powyższego podejścia nie spowoduje przerwania zadania, przyszłość upłynie, ale anulowanie w końcu bloku nie będzie miało wpływu , wątek będzie nasłuchiwał gniazda. Możemy zamknąć gniazdo lub wywołać metodę zamykania przy połączeniu, jeśli jest implementowana przez pulę.
public interface CustomCallable < T > extends Callable < T > {
    void cancel();
    RunnableFuture < T > newTask();
}

public class CustomExecutorPool extends ThreadPoolExecutor {
    protected < T > RunnableFuture < T > newTaskFor(Callable < T > callable) {
        if (callable instanceof CancellableTask)
            return ((CancellableTask < T > ) callable).newTask();
        else
            return super.newTaskFor(callable);
    }
}

public abstract class UnblockingIOTask < T > implements CustomCallable < T > {
    public synchronized void cancel() {
        try {
            obj.close();
        } catch (IOException e) {
            logger.error("io exception", e);
        }
    }

    public RunnableFuture < T > newTask() {
        return new FutureTask < T > (this) {
            public boolean cancel(boolean mayInterruptIfRunning) {
                try {
                    this.cancel();
                } finally {
                    return super.cancel(mayInterruptIfRunning);
                }
            }

        };
    }
}
Gautam Tadigoppula
źródło