Форум программистов, компьютерный форум, киберфорум
Java SE (J2SE)
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.80/5: Рейтинг темы: голосов - 5, средняя оценка - 4.80
52 / 18 / 11
Регистрация: 27.03.2013
Сообщений: 789

ScheduledExecutorService - сбор статистики

19.10.2020, 20:32. Показов 1012. Ответов 11
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Вот небольшой пример приложения.

Сущность.

Кликните здесь для просмотра всего текста


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
public class Account {
 
    private int balance;
 
    private final Lock lock = new ReentrantLock();
 
    /*счетчик неудачных транзакций*/
    private AtomicInteger failCounter = new AtomicInteger(1);
 
 
    public Account(int balance) {
        this.balance = balance;
    }
 
    /*увеличение счетчика неудачных транзакций*/
    public void incrementFailedTransferCounter() {
        this.failCounter.incrementAndGet();
    }
 
    public AtomicInteger getFailCounter() {
        return failCounter;
    }
 
    public Lock getLock() {
        return lock;
    }
 
    /*пополенение счета*/
    public void depositToAccount(final int amount) {
        balance += amount;
    }
 
    /*списание денег с указанного счета*/
    public void withdrawFromAccount(final int amount) {
        balance -= amount;
    }
 
    public int getBalance() {
        return balance;
    }
}


Класс описывающий перевод денег.

Кликните здесь для просмотра всего текста
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
public class Transfer implements Callable<Boolean> {
 
 
    private static final Logger LOGGER =
            LoggerFactory.getLogger(Transfer.class);
 
    private final Account accountFrom;
    private final Account accountTo;
    private final int amount;
    private final int idThread;
 
 
    public Transfer(Account accountFrom, Account accountTo, int amount, int idThread) {
        this.accountFrom = accountFrom;
        this.accountTo = accountTo;
        this.amount = amount;
        this.idThread = idThread;
    }
 
    private final Random waitRandom = new Random();
 
    @Override
    public Boolean call() throws Exception  {
 
        while (true) {
            try {
                boolean isTryLockAccountFrom = accountFrom.getLock().tryLock(LOCK_WAIT_SEC, TimeUnit.SECONDS);
 
                if (isTryLockAccountFrom) {
 
                    boolean isTryLockAccountTo = accountTo.getLock().tryLock(LOCK_WAIT_SEC, TimeUnit.SECONDS);
 
                    if (isTryLockAccountTo) {
                        try {
 
                            if (accountFrom.getBalance() < amount) {
 
                             throw new InsufficientResourcesAccountException("На счету недостаточно денег.");
 
                            }
 
                            accountFrom.withdrawFromAccount(amount);/*списание со счета*/
                            LOGGER.warn("--- Поток № : " + this.idThread);
                            LOGGER.warn("Деньги списаны со счета. Остаток (accountFrom) : "
                                    + accountFrom.getBalance());
 
                            accountTo.depositToAccount(amount);/*пополнение счета*/
                            LOGGER.warn("Деньги зачислены на счет. Остаток (accountTo) : " + accountTo.getBalance());
                            LOGGER.warn("\n");
                            Thread.sleep(waitRandom.nextInt(2000));
 
                            return true;
                        } finally {
                            accountTo.getLock().unlock();
                        }
                    } else {
                        accountTo.incrementFailedTransferCounter();
                        return false;
                    }
                } else {
                    accountFrom.incrementFailedTransferCounter();
                    return false;
                }
            } catch (InterruptedException e) {
                LOGGER.error("Время ожидания попытки" +
                        " захвата блокировки объекта 'isLockAccountTo' - превышено!");
            } finally {
                accountFrom.getLock().unlock();
            }
        }
    }
}


Точка входа в приложение, здесь задаются задачи для потоков и получаются ответы.

Кликните здесь для просмотра всего текста
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
public class TransferRun {
 
    private final static long timeout = 100000;
    private final static TimeUnit unit = TimeUnit.SECONDS;
 
    public static void main(String[] args) throws InterruptedException {
 
        Account accountFrom = new Account(1000);
 
        Account accountTo = new Account(2000);
 
        ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        Random random = new Random();
 
        List<Future<Boolean>> transfersFutureList = new ArrayList<>();
 
        IntStream.range(0, 10)
                .forEach(i -> {
 
                    int sumForTransfer = random.nextInt(400);
 
                    Transfer transfer = new Transfer(accountFrom, accountTo, sumForTransfer, i);
                    Future<Boolean> booleanFuture = executorService.submit(transfer);
 
                    transfersFutureList.add(booleanFuture);
                });
 
        executorService.shutdown();
 
        runScheduleForGettingFailedLocking(accountFrom, accountTo);
 
        /*указываем, сколько времени нужно ждать,
        чтобы все потоки смогли выполниться  */
        try {
            executorService.awaitTermination(timeout, unit);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
 
        showResultFromCallable(transfersFutureList);
    }
 
    private static void runScheduleForGettingFailedLocking(Account accountFrom, Account accountTo) throws InterruptedException {
 
        ScheduledExecutorService scheduledExecutorService  =
                Executors.newScheduledThreadPool(2);
 
        Thread threadAccountFrom = new Thread(() -> {
            AtomicInteger failCounter = accountFrom.getFailCounter();
            System.out.println("Количество неудачных попыток захвата монитора для `accountFrom`:"
                    + failCounter);
        });
 
        scheduledExecutorService
                .scheduleAtFixedRate(threadAccountFrom, 1, 2, TimeUnit.SECONDS);
 
        Thread threadAccountTo = new Thread(() -> {
            AtomicInteger failCounter = accountTo.getFailCounter();
            System.out.println("Количество неудачных попыток захвата монитора для `accountTo`:"
                    + failCounter);
        });
 
 
        scheduledExecutorService
                .scheduleAtFixedRate(threadAccountTo, 1, 2, TimeUnit.SECONDS);
 
        scheduledExecutorService.shutdown();
 
 
        scheduledExecutorService.awaitTermination(timeout, unit);
    }
 
    private static void showResultFromCallable(List< Future<Boolean>> transfersFutureList) {
 
        final List<Boolean> list = new ArrayList<>();
 
        List<Boolean> booleanList = transfersFutureList
                .stream()
                .map(booleanFuture -> {
 
                    Boolean isGetLock;
                    try {
                        isGetLock = booleanFuture.get();
                        list.add(isGetLock);
                    } catch (InterruptedException e) {
                        list.add(false);
                       // e.printStackTrace();
                    } catch (ExecutionException e) {
                        list.add(false);
                       // e.printStackTrace();
                    }
                    return list;
 
                })
                .flatMap(List::stream)
                .collect(Collectors.toList());
                 System.out.println(booleanList);
    }
 
}



Вот этот кусок кода, должен собирать статистику через определенный промежуток времени - о том, сколько неудачных попыток блокировок монитора определенного аккаунта, было за времия выполения задач по переводу денег.

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
 private static void runScheduleForGettingFailedLocking(Account accountFrom, Account accountTo) throws InterruptedException {
 
        ScheduledExecutorService scheduledExecutorService  =
                Executors.newScheduledThreadPool(2);
 
        Thread threadAccountFrom = new Thread(() -> {
            AtomicInteger failCounter = accountFrom.getFailCounter();
            System.out.println("Количество неудачных попыток захвата монитора для `accountFrom`:"
                    + failCounter);
        });
 
        scheduledExecutorService
                .scheduleAtFixedRate(threadAccountFrom, 1, 2, TimeUnit.SECONDS);
 
        Thread threadAccountTo = new Thread(() -> {
            AtomicInteger failCounter = accountTo.getFailCounter();
            System.out.println("Количество неудачных попыток захвата монитора для `accountTo`:"
                    + failCounter);
        });
 
 
        scheduledExecutorService
                .scheduleAtFixedRate(threadAccountTo, 1, 2, TimeUnit.SECONDS);
 
        scheduledExecutorService.shutdown();
 
 
        scheduledExecutorService.awaitTermination(timeout, unit);
    }
можно сделать так:

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
    private static ScheduledExecutorService runScheduleForGettingFailedLocking(Account accountFrom, Account accountTo) throws InterruptedException {
 
        ScheduledExecutorService scheduledExecutorService  =
                Executors.newScheduledThreadPool(2);
 
        Thread threadAccountFrom = new Thread(() -> {
            AtomicInteger failCounter = accountFrom.getFailCounter();
            System.out.println("Количество неудачных попыток захвата монитора для `accountFrom`:"
                    + failCounter);
        });
 
        scheduledExecutorService
                .scheduleAtFixedRate(threadAccountFrom, 0, 2, TimeUnit.SECONDS);
 
        Thread threadAccountTo = new Thread(() -> {
            AtomicInteger failCounter = accountTo.getFailCounter();
            System.out.println("Количество неудачных попыток захвата монитора для `accountTo`:"
                    + failCounter);
        });
 
 
        scheduledExecutorService
                .scheduleAtFixedRate(threadAccountTo, 0, 2, TimeUnit.SECONDS);
 
        scheduledExecutorService.shutdown();
 
 
        scheduledExecutorService.awaitTermination(timeout, unit);
 
       return scheduledExecutorService;
    }

в точке входа вот так :

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 static void main(String[] args) throws InterruptedException {
 
        Account accountFrom = new Account(1000);
 
        Account accountTo = new Account(2000);
 
        ScheduledExecutorService scheduledExecutorService =
                runScheduleForGettingFailedLocking(accountFrom, accountTo);
 
        ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        Random random = new Random();
 
...
 
      executorService.shutdown();
 
        runScheduleForGettingFailedLocking(accountFrom, accountTo);
 
        /*указываем, сколько времени нужно ждать,
        чтобы все потоки смогли выполниться  */
        try {
            executorService.awaitTermination(timeout, unit);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        scheduledExecutorService.shutdown();
 
        showResultFromCallable(transfersFutureList);
    }
То есть первым, мы определили сервис, который запускает задачу по расписанию.
Но все равно не понимаю, что происходит.

Похоже, что сервис этот запускается так быстро, что не успевает собрать статистику ?

Но результат работы данного метода, я не вижу.

Кто глубоко разбирался в java.util.concurrency, может объясните в чем ошибка. Разъясните пожалуйста, как работает механизм отложенного запуска потоков и работу их через определенные промежутки времени.

Может в текущем контексте предложите лучшее решение ?
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
19.10.2020, 20:32
Ответы с готовыми решениями:

ScheduledExecutorService останавливается
Добрый день. Коротко - есть класс &quot;диалогового облачка&quot;, в котором мне нужно выполнять код раз в 75 мс Использую следующее решение...

Повторяющееся событие, ScheduledExecutorService
Здравствуйте. Поясните пожалуйста, как можно реализовать уведомления, которые повторяются с некоторым интервалом? Например, каждые 5...

Сбор статистики
Не знаю пишеться это на PHP или на Java scrips, думаю что на PHP. Дело состоит вот в чем мне надо написать код который бы собирал...

11
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
19.10.2020, 20:34
Цитата Сообщение от masli Посмотреть сообщение
Но результат работы данного метода, я не вижу.
Ну так ты ж его сразу останавливаешь:
Цитата Сообщение от masli Посмотреть сообщение
Java
1
scheduledExecutorService.shutdown();
Цитата Сообщение от masli Посмотреть сообщение
Java
1
Thread threadAccountFrom = new Thread(() -> {
Зачем треды создаёшь?

Цитата Сообщение от masli Посмотреть сообщение
Сущность.
Зачем там вообще лок, если методы его не используют?
0
52 / 18 / 11
Регистрация: 27.03.2013
Сообщений: 789
19.10.2020, 23:21  [ТС]
Цитата Сообщение от korvin_ Посмотреть сообщение
Зачем там вообще лок, если методы его не используют?
а это что ?

Java
1
 accountTo.getLock().tryLock(LOCK_WAIT_SEC, TimeUnit.SECONDS)
0
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
19.10.2020, 23:26
Цитата Сообщение от masli Посмотреть сообщение
а это что ?
А это находится вне методов Account. Что мешает дёргать depositToAccount и withdrawFromAccount в обход этих локов?
0
52 / 18 / 11
Регистрация: 27.03.2013
Сообщений: 789
19.10.2020, 23:34  [ТС]
shutdown(),- ждет завершения запущенных задач

shutdownNow()- останавливает исполнитель немедленно


Когда происходит вызов метода shutdown(), то сначала завершаться все потоки, которые уже были поставлены в качестве задач для ExecutorService и только затем будет остановлено выполнение задач через ExecutorService.

shutdownNow() – когда происходит вызов данного метода, тогда остановка выполнения задач происходит сразу и потоки которые были в задачах и стояли в очереди на выполнение, - не будут выполняться.
Таким потокам будет выставлен статус interrupt (потому что в java нет метода, который может внезапно остановить потоки)

Добавлено через 2 минуты
Цитата Сообщение от korvin_ Посмотреть сообщение
Что мешает дёргать depositToAccount и withdrawFromAccount в обход этих локов?
тогда получим несогласованность данных

Добавлено через 1 минуту
Java
1
2
3
 
    Account accountFrom;
    Account accountTo;
Это разделяемые ресурсы, используемые в потоках
0
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
20.10.2020, 01:30
Цитата Сообщение от masli Посмотреть сообщение
тогда получим несогласованность данных
Ну так может ты сделаешь эти методы потокобезопасными?

Цитата Сообщение от masli Посмотреть сообщение
Это разделяемые ресурсы, используемые в потоках
Только они не потокобезопасны.

Цитата Сообщение от masli Посмотреть сообщение
shutdown(),- ждет завершения запущенных задач
Это если есть выполняющиеся задачи. А если они ожидают запуска, то сервис сразу выключается.

Цитата Сообщение от masli Посмотреть сообщение
Похоже, что сервис этот запускается так быстро, что не успевает собрать статистику?
Похоже, что цикл от 0 до 9 — слишком простая задача, чтобы выполняться хоть какое-то значимое время. Не находишь?
0
52 / 18 / 11
Регистрация: 27.03.2013
Сообщений: 789
20.10.2020, 09:59  [ТС]
Цитата Сообщение от korvin_ Посмотреть сообщение
Ну так может ты сделаешь эти методы потокобезопасными?
а чем плох подход с использованием ReentrantLock ?

Я пока не понял, что ты хочешь донести. Почему я должен разделить именно эти методы и использовать synchronized блоки ?
Мне нужна будет синхронизация на обоих ресурсах. А как избежать deadlock при использовании такого подхода ( synchronized блоки) если мы разных потоках поменяем местами разделяемые ресурсы? Кроме того использования такой блокировки (synchronized блоки) снижает производительность на больших объемах данных.

А пример выше, использует tryLock(), который блокирует только в определенное время. Мы можем поставить блокировку в одном методе и затем можем снять в другом. Здесь же используется оптимистическая блокировка, или я не прав ?
0
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
20.10.2020, 10:16
Цитата Сообщение от masli Посмотреть сообщение
а чем плох подход с использованием ReentrantLock?
ReentrantLock не плох, просто он торчит у тебя наружу и никак не защищает методы объекта:

Java
1
2
3
4
5
6
7
Account a = new Account(100);
new Thread(() -> {
    // lock
    a.withdrawFromAccount(60);
    // unlock
});
a.withdrawFromAccount(60);
и вот у тебя отрицательный баланс.

Цитата Сообщение от masli Посмотреть сообщение
Почему я должен разделить именно эти методы и использовать synchronized блоки?
Я ничего не говорил про synchronized-блоки.

Цитата Сообщение от masli Посмотреть сообщение
А как избежать deadlock при использовании такого подхода ( synchronized блоки) если мы разных потоках поменяем местами разделяемые ресурсы?
Подумай.

Цитата Сообщение от masli Посмотреть сообщение
Кроме того использования такой блокировки (synchronized блоки) снижает производительность на больших объемах данных.
А ReentrantLock не снижает? Ну-ну. То же самое. Тем более ты блокируешь два ресурса одновременно, скорее всего даже хуже будет, потому что блокировка более долгая.

Цитата Сообщение от masli Посмотреть сообщение
Здесь же используется оптимистическая блокировка, или я не прав?
Нет, не прав. Оптимистичная блокировка используется в AtomicInteger.compareAndSet, например.
0
52 / 18 / 11
Регистрация: 27.03.2013
Сообщений: 789
20.10.2020, 10:30  [ТС]
Цитата Сообщение от korvin_ Посмотреть сообщение
и вот у тебя отрицательный баланс.
это я и добивался, имитацию отрицательного баланса

Java
1
 IntStream.range(0, 1000000)
я увеличил время, но это не позволило увидеть статистику

Добавлено через 2 минуты
но здесь вопрос не в то, чтобы сделать синхронизацию другим способом и увидеть статистику, а вопрос в том, как это сделать средствами, представленными в примере выше. Я так и не понял, почему планировщик не работает.
0
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
20.10.2020, 16:31
Цитата Сообщение от masli Посмотреть сообщение
но здесь вопрос не в то, чтобы сделать синхронизацию другим способом и увидеть статистику, а вопрос в том, как это сделать средствами, представленными в примере выше. Я так и не понял, почему планировщик не работает.
Потому что ты его shutdown'ишь. Я же написал. Сделай sleep на 10 секунд, а потом вызывай shutdown.
1
52 / 18 / 11
Регистрация: 27.03.2013
Сообщений: 789
20.10.2020, 17:42  [ТС]
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
       
....
 scheduledExecutorService
                .scheduleAtFixedRate(threadAccountTo, 1, 2, TimeUnit.SECONDS);
 
        Thread.sleep(20000);
 
        scheduledExecutorService.shutdown();
 
        scheduledExecutorService.awaitTermination(timeout, unit);
 
       return scheduledExecutorService;
    }
Да, теперь планировщик сработал. Я предполагал, что shutdown() просто останавливает прием задач, но те задачи что были заданы, должны в любом случае выполниться....


И почему ты считаешь, что Lock выставлен "наружу".... поясни разницу пожалуйста.
0
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
20.10.2020, 17:49
Цитата Сообщение от masli Посмотреть сообщение
И почему ты считаешь, что Lock выставлен "наружу"
Я не считаю, у тебя код так написан:

Java
1
2
3
    public Lock getLock() {
        return lock;
    }
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
20.10.2020, 17:49
Помогаю со студенческими работами здесь

Сбор статистики
Доброе время суток!У меня проект энергопотребление кирпичного завода.Нужно считать сколько затрачивает энергии каждое оборудование,для...

Сбор статистики
Добрый вечер) возникли проблемы в отражении на графике - загруженность рабочих(ресурс). по справке ссылают на элемент PML, но можно ли...

Сбор статистики
Есть 2000 человек, на каждого человека есть карта. Необходимо иметь информацию о том, какой продукт был зарегистрирован по этой карте, его...

Сбор статистики по очереди
Есть программа написанная на GPSS: GENERATE (EXPONENTIAL(1,0,1)) QUEUE ALL_0 QUEUE Q_1 ADVANCE (EXPONENTIAL(1,0,1)) DEPART...

Сбор сетевой статистики
Здравствуйте. Есть VDS с Ubuntu 16.04.1 LTS. Нужно собирать сетевую статистику (в фоне) сколько трафика отправлено/получено и какие...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
12
Ответ Создать тему
Новые блоги и статьи
Управление камерой с помощью скрипта OrbitControls.js на Three.js: Вращение, зум и панорамирование
8Observer8 05.03.2026
Содержание блога Финальная демка в браузере работает на Desktop и мобильных браузерах. Итоговый код: orbit-controls-threejs-js. zip. Сканируйте QR-код на мобильном. Вращайте камеру одним пальцем,. . .
SDL3 для Web (WebAssembly): Синхронизация спрайтов SDL3 и тел Box2D
8Observer8 04.03.2026
Содержание блога Финальная демка в браузере. Итоговый код: finish-sync-physics-sprites-sdl3-c. zip На первой гифке отладочные линии отключены, а на второй включены:. . .
SDL3 для Web (WebAssembly): Идентификация объектов на Box2D v3 - использование userData и событий коллизий
8Observer8 02.03.2026
Содержание блога Финальная демка в браузере. Итоговый код: finish-collision-events-sdl3-c. zip Сканируйте QR-код на мобильном и вы увидите, что появится джойстик для управления главным героем. . . .
Реалии
Hrethgir 01.03.2026
Нет, я не закончил до сих пор симулятор. Эта задача сложнее. Не получилось уйти в плавсостав, но оно и к лучшему, возможно. Точнее получалось - но сварщиком в палубную команду, а это значит, в моём. . .
Ритм жизни
kumehtar 27.02.2026
Иногда приходится жить в ритме, где дел становится всё больше, а вовлечения в происходящее — всё меньше. Плотный график не даёт вниманию закрепиться ни на одном событии. Утро начинается с быстрых,. . .
SDL3 для Web (WebAssembly): Сборка библиотек: SDL3, Box2D, FreeType, SDL3_ttf, SDL3_mixer и SDL3_image из исходников с помощью CMake и Emscripten
8Observer8 27.02.2026
Недавно вышла версия 3. 4. 2 библиотеки SDL3. На странице официальной релиза доступны исходники, готовые DLL (для x86, x64, arm64), а также библиотеки для разработки под Android, MinGW и Visual Studio. . . .
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru