Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Java [игнор отключен] [закрыт для гостей] / Как отследить завершение всех потоков / 24 сообщений из 24, страница 1 из 1
15.03.2016, 09:45
    #39191930
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Приветствую. Использую класс наследованный от Runnable для создание потоков. Запуск через кнопку. После запуска кнопка становится неактивной. После выполнения всех потоков требуется сделать ее активной. Как отследить завершение всех потоков? Или может посоветуете более продвинутый класс для работы с потоками?
...
Рейтинг: 0 / 0
15.03.2016, 10:13
    #39191944
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxixИспользую класс наследованный от Runnable для создание потоков.
Начинаем с заблуждения. Runnable, так же как и Callable это не "создание потоков". Это реализация определенной задачи, которую вы можете выполнять в разных потоках. Научитесь отделять поток от самой задачи, которую он выполняет.

saxixЗапуск через кнопку. После запуска кнопка становится неактивной.

Swing?

saxixПосле выполнения всех потоков требуется сделать ее активной.

Что мешает?

saxixКак отследить завершение всех потоков?
Ну, если в лоб, то дописать в конце каждого метода run() некий, код. Когда все методы его выполнят, запустить событие активации кнопки. Это может быть атомарный счетчик, при запуске задачи вы его инкрементируете, при завершении, соответственно декрементируете. При обнулении, выполняете нужный вам код.
По-хорошему, конечно, надо открыть для себя ExecutorService и Future.

saxixИли может посоветуете более продвинутый класс для работы с потоками?
SwingWorker либо аналогичный класс для вашего GUI фреймверка.
...
Рейтинг: 0 / 0
15.03.2016, 10:17
    #39191947
Petro123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxix,
Что за поток что кнопка не отжалась?
Или это фича свинга?
...
Рейтинг: 0 / 0
15.03.2016, 10:50
    #39191981
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Petro123saxix,
Что за поток что кнопка не отжалась?
Или это фича свинга?
Я не писал что она не отжалась. Стормозил малость. Подумал что фокус с атомарным счетчиком не пройдет.
...
Рейтинг: 0 / 0
15.03.2016, 10:51
    #39191983
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Blazkowicz,
попробую ServiceExecutor
...
Рейтинг: 0 / 0
15.03.2016, 11:00
    #39191999
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxixпопробую ServiceExecutor
Ну, изобретете свой SwingWorker в итоге. Так почему бы не взять готовый?
...
Рейтинг: 0 / 0
15.03.2016, 11:07
    #39192009
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxixЯ не писал что она не отжалась. Стормозил малость. Подумал что фокус с атомарным счетчиком не пройдет.
Кстати, в JUC есть такой класс.
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html

Проблема только в том, что в GUI нужно не ждать пока что-то закончится, а правильно обрабатывать синхронные колбэки. Поэтому большинство JUC классов не особо полезны.
...
Рейтинг: 0 / 0
15.03.2016, 11:21
    #39192025
WGA
WGA
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Если Java 8, то можно воспользоваться CompletableFuture

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
public class Main {

    public static void main(String[] args) throws Exception {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            sleep(1000L);
            return 1;
        });
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            sleep(2000L);
            return 2;
        });
        CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
            sleep(3000L);
            return 3;
        });
        CompletableFuture<Void> together = CompletableFuture.allOf(future1, future2, future3);
        together.get();

        System.out.println(future1.get());
        System.out.println(future2.get());
        System.out.println(future3.get());
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
...
Рейтинг: 0 / 0
15.03.2016, 11:23
    #39192026
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Blazkowiczsaxixпопробую ServiceExecutor
Ну, изобретете свой SwingWorker в итоге. Так почему бы не взять готовый?
Пытаюсь проанализировать, с чем быстрее реализовать с учетом времени на изучение
...
Рейтинг: 0 / 0
15.03.2016, 11:33
    #39192042
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
WGAЕсли Java 8, то можно воспользоваться CompletableFuture
Код: java
1.
2.
        CompletableFuture<Void> together = CompletableFuture.allOf(future1, future2, future3);
        together.get();


С тем же успехом можно и в цикле по обычным Future блокироваться. Проблема GUI в другом. EDT блокировать нельзя.

Вообще у меня есть ещё одно решение в лоб.
Можно создать новый поток, на него заджоинить все остальные, а в нём просто пульнуть событие в EventQueue. Но это всё равно коряво.
...
Рейтинг: 0 / 0
15.03.2016, 11:36
    #39192050
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxixПытаюсь проанализировать, с чем быстрее реализовать с учетом времени на изучение
Покажите что ли код. Потому как концептуально архитектурно нужно вообще всё иначе делать.
Потоки выполняют некую задачу и обновляют модель уже в EDT через SwingWorker. И это уже задача модели определить все ли данные готовы. Задачу как бы от потоков нужно вообще абстрагировать. Не важно ведь сколько там потоков один, или десять, важно, то готова ли модель к тому чтобы активировать кнопку или нет.

Поэтому все извращения с JUC для архитектуры GUI только во вред.
...
Рейтинг: 0 / 0
15.03.2016, 11:43
    #39192063
Сергей Арсеньев
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
В общем случае можно сделать некую шину и два события на ней. Кнопка подписывается на события. И при аттачменте посылает первое событие, по которому шедулер отвечает в шину сообщением второго типа с текущим состоянием.
Шедулер так же потоков пуляет второе сообщение и по изменению своего состояния.
Таким образом имеем разное количество кнопок, которые переключают свою доступность по состоянию шедулера.
В таком примитивном варианте несложно делается самим на примитивах, но можно и стандартными методами. :)
...
Рейтинг: 0 / 0
15.03.2016, 12:09
    #39192113
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
BlazkowiczsaxixПытаюсь проанализировать, с чем быстрее реализовать с учетом времени на изучение
Покажите что ли код. Потому как концептуально архитектурно нужно вообще всё иначе делать.
Потоки выполняют некую задачу и обновляют модель уже в EDT через SwingWorker. И это уже задача модели определить все ли данные готовы. Задачу как бы от потоков нужно вообще абстрагировать. Не важно ведь сколько там потоков один, или десять, важно, то готова ли модель к тому чтобы активировать кнопку или нет.

Поэтому все извращения с JUC для архитектуры GUI только во вред.
Да я только тестовый накидал, и то уже поправил на ServiceExecutor. Так сказать - пытаюсь понять что написал.
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
package test;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

class Executor {
    private JButton button;

    JButton getButton() {
        return button;
    }

    void setButton(JButton button) {
        this.button = button;
    }

    void task(int id) throws InterruptedException {
        Random random = new Random();
        int timer = random.nextInt(5);
        Thread.sleep(1000 * timer);
        System.out.println("Задача [" + id + "] выполнена: " + new Date());
    }

    void run() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        List<Future> futureList = new ArrayList<Future>();

        System.out.println("Кнопка не активна");
        button.setEnabled(false);
        for (int x = 0; x < 10; x++) {
            final int id = x;
            futureList.add(executorService.submit(new Runnable() {
                public void run() {
                    try {
                        task(id);
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
            }));
        }
        executorService.shutdown();
        executorService.awaitTermination(10, TimeUnit.HOURS);
        System.out.println("Кнопка активна");
        button.setEnabled(true);
    }
}

public class TestButtonEnabled extends JFrame {
    private JButton getButton() {
        JButton button = new JButton("test");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    Executor executor = new Executor();
                    executor.setButton(button);
                    executor.run();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
        });
        return button;
    }

    private static void createAndShowGui() {
        TestButtonEnabled testButtonEnabled = new TestButtonEnabled();
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(testButtonEnabled.getButton());
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}
...
Рейтинг: 0 / 0
15.03.2016, 12:23
    #39192133
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxix
Код: java
1.
2.
        executorService.shutdown();
        executorService.awaitTermination(10, TimeUnit.HOURS);


Это вообще не то. Здесь должно быть
Код: java
1.
2.
3.
for (Future f : futureList){
      f.get();         
}



Но, оно для GUI всё равно не подходит, потому что блокирует EDT в обоих случаях.
...
Рейтинг: 0 / 0
15.03.2016, 12:26
    #39192139
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Blazkowiczsaxix
Код: java
1.
2.
        executorService.shutdown();
        executorService.awaitTermination(10, TimeUnit.HOURS);


Это вообще не то. Здесь должно быть
Код: java
1.
2.
3.
for (Future f : futureList){
      f.get();         
}



Перед отправкой поменял.
BlazkowiczНо, оно для GUI всё равно не подходит, потому что блокирует EDT в обоих случаях

Что это значит?
...
Рейтинг: 0 / 0
15.03.2016, 12:31
    #39192146
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxixЧто это значит?
Это значит что в момент, когда вы будете ждать завершения потоков, вы блокируете текущий поток. А текущий поток это Event Dispatch Thread, на котором работает GUI. Блокировка этого потока приводит к блокировке всего UI и никакого профита от многопоточности не будет. Вы пример разве не запускали? Фриза на секунду разве нет? Обычно после такого сразу начинаются жалобы "Swing тормозит".
...
Рейтинг: 0 / 0
15.03.2016, 12:33
    #39192153
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
BlazkowiczsaxixЧто это значит?
Это значит что в момент, когда вы будете ждать завершения потоков, вы блокируете текущий поток. А текущий поток это Event Dispatch Thread, на котором работает GUI. Блокировка этого потока приводит к блокировке всего UI и никакого профита от многопоточности не будет. Вы пример разве не запускали? Фриза на секунду разве нет? Обычно после такого сразу начинаются жалобы "Swing тормозит".
Тормозов, если честно, не заметил. Формочка свободно перетаскивается по экрану
...
Рейтинг: 0 / 0
15.03.2016, 12:37
    #39192162
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxix,

К вашей проблеме сложно дать правильный совет, потому что с одной стороны у вас GUI, а с другой стороны, вроде как GUI вам особо не нужен, вы пытаетесь проблему запуска решить. Правильность решения сильно зависит от того зачем мы запускаем именно несколько фоновых задач.

Можно запустить 1 SwingWorker на каждую задачу.
Можно запустить 1 SwingWorker для все задачи, а внутри doInBackground уже запускать фоновые задачи. Там же и заблокировать SwingWorker ожидая их завершения.
Может вообще нужен ForkJoinPool, если результат фоновых задач нужно собрать воедино.
...
Рейтинг: 0 / 0
15.03.2016, 12:39
    #39192167
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Blazkowiczsaxix,

К вашей проблеме сложно дать правильный совет, потому что с одной стороны у вас GUI, а с другой стороны, вроде как GUI вам особо не нужен, вы пытаетесь проблему запуска решить. Правильность решения сильно зависит от того зачем мы запускаем именно несколько фоновых задач.

Можно запустить 1 SwingWorker на каждую задачу.
Можно запустить 1 SwingWorker для все задачи, а внутри doInBackground уже запускать фоновые задачи. Там же и заблокировать SwingWorker ожидая их завершения.
Может вообще нужен ForkJoinPool, если результат фоновых задач нужно собрать воедино.
Могу только послушать Вашего совета. Попробую реализовать второй вариант
...
Рейтинг: 0 / 0
15.03.2016, 12:41
    #39192171
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxix,

И ещё не понятно, с одной стороны у вас Java 8, с другой стороны ни лямбд, ни Diamond Operator я не вижу.
...
Рейтинг: 0 / 0
15.03.2016, 12:44
    #39192179
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
saxixТормозов, если честно, не заметил. Формочка свободно перетаскивается по экрану
Двигает окно операционка. Swing это событие лишь слушает.
Попробуйте изменить размер окна. Попробуйте добавить текстовое поле и вбивать в него текст во время процесса.
В конце концов, добавьте JProgressBar, тогда-то косяки и полезут наружу.
...
Рейтинг: 0 / 0
15.03.2016, 12:48
    #39192184
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Blazkowiczsaxix,
И ещё не понятно, с одной стороны у вас Java 8, с другой стороны ни лямбд, ни Diamond Operator я не вижу.
Использование 8-ки не обязательное ж условие на использование лямбд. Могу и переделать.
...
Рейтинг: 0 / 0
15.03.2016, 13:13
    #39192214
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

/**
 * This class represents...
 */
public class AsyncUI {

    static class Model {
        int level;

        public void setLevel(int level) {
            System.out.println(level);
            int old = this.level;
            this.level = level;
            pcs.firePropertyChange("level", old, level);
        }

        public void decrement() {
            setLevel(this.level - 1);
        }

        private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            this.pcs.addPropertyChangeListener(listener);
        }

        public void removePropertyChangeListener(PropertyChangeListener listener) {
            this.pcs.removePropertyChangeListener(listener);
        }
    }

    static class AsyncModelDecrementer extends SwingWorker<Void, Void>{
        private final int time;

        public AsyncModelDecrementer(int x) {
            this.time = x;
        }

        @Override
        protected Void doInBackground() throws Exception {
            Thread.sleep(time * 1000);
            return null;
        }

        @Override
        protected void done() {
            model.decrement();
        }
    }

    static class BackgroundProcessAction extends AbstractAction{

        public BackgroundProcessAction() {
            super("Background Process");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int asyncLevel = 10;
            model.setLevel(asyncLevel);
            for (int x = 0; x < asyncLevel; x++) {
                new AsyncModelDecrementer(x).execute();
            }
        }
    }


    static final Model model = new Model();


    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        JButton btn = new JButton(new BackgroundProcessAction());
        JProgressBar progress = new JProgressBar();
        progress.setStringPainted(true);

        //Do not forget to remove listener when UI disposed
        model.addPropertyChangeListener(evt -> {
            Integer newLevel = (Integer) evt.getNewValue();
            boolean inProgress = newLevel > 0;
            btn.setEnabled(!inProgress);
            progress.setString(newLevel.toString());
            progress.setIndeterminate(inProgress);
        });
        model.setLevel(0);

        frame.add(btn, BorderLayout.CENTER);
        frame.add(progress, BorderLayout.SOUTH);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }
}




В идеале, конечно, то что в Main тоже должно быть отдельным классом.
...
Рейтинг: 0 / 0
15.03.2016, 16:41
    #39192533
saxix
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Как отследить завершение всех потоков
Blazkowicz,
попытался переделать под свои нужды, упростил
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
package test;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Random;

class Counter {
    private int threadRunCounter;

    public int getThreadRunCounter() {
        return threadRunCounter;
    }

    public void setThreadRunCounter(int threadRunCounter) {
        System.out.println("Запущено процессов: " + threadRunCounter);
        int old = this.threadRunCounter;
        this.threadRunCounter = threadRunCounter;
        pcs.firePropertyChange("threadRunCounter", old, threadRunCounter);
    }

    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(listener);
    }

    public void threadClosed() {
        this.setThreadRunCounter(this.threadRunCounter - 1);
    }
}

class MyWorker extends SwingWorker<Void, Void> {
    private Counter counter;
    private String processName;

    public MyWorker(Counter counter, String processName) {
        this.counter = counter;
        this.processName = processName;
    }

    @Override
    protected Void doInBackground() throws Exception {
        Random r = new Random();
        int time = 1000 * r.nextInt(5);
        Thread.sleep(time);
        return null;
    }

    @Override
    protected void done() {
        System.out.println(this.processName + " - done");
        counter.threadClosed();
    }
}

class BackgrProcessAction extends AbstractAction {
    private Counter counter;
    private int threadCount;

    public BackgrProcessAction(int threadCount, Counter counter) {
        this.counter = counter;
        this.threadCount = threadCount;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        counter.setThreadRunCounter(threadCount);
        for (int idx = 0; idx < threadCount; idx++) {
            new MyWorker(counter, "Процесс №" + idx).execute();
        }
    }
}

public class RunnableClass extends JFrame {

    private static final int THREAD_COUNT = 10;

    private static void createAndShowGui() {
        RunnableClass runnableClass = new RunnableClass();
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        Counter counter = new Counter();
        JButton jButton = new JButton(new BackgrProcessAction(THREAD_COUNT, counter));
        jButton.setText("Запуск");
        frame.add(jButton);

        counter.addPropertyChangeListener(evt -> {
            jButton.setEnabled(!((Integer) evt.getNewValue() > 0));
        });


        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}
...
Рейтинг: 0 / 0
Форумы / Java [игнор отключен] [закрыт для гостей] / Как отследить завершение всех потоков / 24 сообщений из 24, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]