Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/3: Рейтинг темы: голосов - 3, средняя оценка - 5.00
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3421 / 2740 / 575
Регистрация: 04.09.2018
Сообщений: 8,608
Записей в блоге: 3

TPL взаимодействие с блокировкой

20.09.2024, 20:17. Показов 772. Ответов 10
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Всем привет.
Есть некий сервис, типа такого:
class Service

C#
1
2
3
4
5
6
7
8
9
10
11
class Service
{
    public void Write(string msg) => Console.WriteLine(msg);    //Запись без чтения (быстро)
 
    public async Task<bool> WriteRead(string msg)   //Запись - чтение (медленно)
    {
        Write(msg);
        await Task.Delay(500);  //Имитация задержки чтения
        return false;
    }
}

и главный класс, который его использует:
class ClassMain

C#
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
class ClassMain
{
    readonly Service service = new Service();
 
    bool busy;  // Флаг "Сервис занят"
 
    int i = 0;
 
    // Метод для внешнего взаимодействия с сервисом
    public void WriteToService(string msg)
    {
        if (!busy)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            service.Write(msg);
            Console.ResetColor();
        }
    }
 
 
    public ClassMain()
    {
        Task.Run(async () =>
        {
            while (true)
            {
                busy = true;
                busy = await service.WriteRead($"Main write -> {++i}");
 
                await Task.Delay(1000); // "Окно" для записи через внешний метод WriteToService
            }
        });
    }
}

Так же, этот класс предоставляет метод для "внешнего" взаимодействия с Сервисом.
Очевидная проблема здесь в том, что если кто-то извне запросит действие с Сервисом, но этот сервис в этот момент будет занят (busy), то внешние данные для записи потеряются.
Main

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void Main()
{
    ClassMain main = new ClassMain();
    int i = 0;
 
    while (true)
    {
        ConsoleKeyInfo info = Console.ReadKey();
        if (info.Key == ConsoleKey.Escape)
        {
            main.Stop();
            break;
        }
 
        // Другой поток взаимодействует с сервисом...
        main.WriteToService($"Extern write -> {++i}");
    }
 
    Console.ReadLine();
}

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

т.е. внешняя входная последовательность не полная, стрелками показаны участки с пропущенными данными.
Попробуем избавится от этого, введя в качестве "синхронизатора" объект ManualResetEventSlim:
class ClassMain С СИНХРОНИЗАЦИЕЙ

C#
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
class ClassMain
{
    readonly Service service = new Service();
    bool work = true;
    bool busy;  // Флаг "Сервис занят"
 
    int i = 0;
 
    // "Флаг - Синхронизатор"
    readonly ManualResetEventSlim flag = new ManualResetEventSlim(false);
 
    public void Stop() => work = false;
 
 
    // Метод для внешнего взаимодействия с сервисом
    public void WriteToService(string msg)
    {
        // Если сервис занят - ждем!
        if (busy)
            flag.Wait();
 
        Console.ForegroundColor = ConsoleColor.Red;
        service.Write(msg);
        Console.ResetColor();
        flag.Reset();
    }
 
 
    public ClassMain()
    {
        Task.Run(async () =>
        {
            while (work)
            {
                busy = true;
                busy = await service.WriteRead($"Main write -> {++i}");
 
                flag.Set(); // "Сервис свободен"
 
                await Task.Delay(1000); // "Окно" для записи через внешний метод WriteToService
            }
        });
    }
}

Ситуация становится лучше:
Кликните здесь для просмотра всего текста

или так:
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main()
{
    ClassMain main = new ClassMain();
 
    for (int i = 0; i < 1000; i++)
    {
        main.WriteToService($"Extern write -> {i}");
        Thread.Sleep(200);
    }
 
    main.Stop();
    Console.ReadLine();
}

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

Внешние данные теперь не теряются, а отправляются, как только становится доступно "окно" работы с сервисом.

Теперь вопрос: не слишком ли это более "тяжелое" решение по сравнению с тем же локом (lock)? И, вероятно, лочить нужно будет уже блок кода самого провайдера (тот, кто обеспечивает непосредственный обмен). Однако, это вряд ли гарантирует сохранность данных, которые пришли в момент занятости сервиса..
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
20.09.2024, 20:17
Ответы с готовыми решениями:

Ошибки в файлах шаблонах header.tpl, footer.tpl
При установке нового шаблона появились косяки, а именно появились ошибки в файлах шаблонах header.tpl, footer.tpl должен по дефолту...

Проблемы с блокировкой <select>
Всем привет!!! У меня в форме есть два поля типа &lt;select&gt;&lt;option&gt;&lt;/option&gt;&lt;/select&gt;. Мне необходимо написать скрипт, который...

Проблема с блокировкой файла
Добрый день создал прогу которая делает скрины и отправляет на сервер через tcp, сервер вставил в winforms после того как массив байтов...

10
HF
 Аватар для HF
1316 / 895 / 200
Регистрация: 09.09.2011
Сообщений: 2,688
Записей в блоге: 2
20.09.2024, 21:14
- сервис всё-таки читает или только пишет? Если только пишет, то почему WriteRead назвается?
- если это не пример, а сервис только и пишет и даже результат не нужен (везде void), то зачем эти ожидания.
Набиваем стек "задач" и в цикле их выполняем. Только цикл берёт "задачу", ему только надо один раз залочить на чтении очереди.
Не?
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3421 / 2740 / 575
Регистрация: 04.09.2018
Сообщений: 8,608
Записей в блоге: 3
20.09.2024, 21:28  [ТС]
HF, это весьма облегченная интерпретация в консоли настоящих классов в продакшене. Здесь нет надобности приводить полную картину - смысл соблюдается и в приведенном примере.

Сервис - и пишет и читает. Может только писать (отослать команду), а может и прочитать ответ. Запись условно-мгновенная, ответ тоже очень быстрый. Но именно в этот момент, кто-то снаружи может что-то попытаться записать, но обломится...

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

Добавлено через 1 минуту
Цитата Сообщение от HF Посмотреть сообщение
почему WriteRead назвается
главный класс, где этот сервис используется - сначала пишет, затем читает. Он постоянно запрашивает данные. Они нужны только ему. А все остальное может только записать (отправить команду). Читать результат им не нужно.
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3421 / 2740 / 575
Регистрация: 04.09.2018
Сообщений: 8,608
Записей в блоге: 3
20.09.2024, 22:29  [ТС]
Немного проясню ситуацию, для понимания.
Времена опросов (частоты) сервиса и внешних процессов когерентны. Т.е. не возникнет ситуации, когда за один период кто-то осуществит операцию дважды.
Только у внешних запросов может произойти иногда смещение по времени (по разным причинам), и запрос может попасть в момент, когда "занято".
Это поясняется на следующем рисунке:
0
HF
 Аватар для HF
1316 / 895 / 200
Регистрация: 09.09.2011
Сообщений: 2,688
Записей в блоге: 2
20.09.2024, 22:54
Цитата Сообщение от wizard41 Посмотреть сообщение
Запись условно-мгновенная, ответ тоже очень быстрый. Но именно в этот момент, кто-то снаружи может что-то попытаться записать, но обломится...
Стек и очередь будут излишними, т.к. в них максимум будет только один элемент.
"Мы стараемся всех обслужить. Но видите же у нас не хватает рук."
То есть вы точно знаете что будет точно не максимум один элемент, но противитесь этому и хотите задержать новых и даже обломать их, но... придумываете как бы их так задержать, чтобы они не ругались и не писали заведующему заявления о недоброжелательном отношении и вообще об отсутствии мест в кабинет.
1
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
20.09.2024, 23:13
Цитата Сообщение от wizard41 Посмотреть сообщение
не слишком ли это более "тяжелое" решение по сравнению с тем же локом (lock)?
await нельзя использовать в блоке lock, потому вопрос отпадает
В принципе использовать блокирующие операции в асинхронном коде не рекомендуется — нивелирует весь профит асинхронности.
Можете использовать SemaphoreSlim — у него есть методы для асинхронного ожидания. Ну а Slim в имени намекает на легковесность.
1
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3421 / 2740 / 575
Регистрация: 04.09.2018
Сообщений: 8,608
Записей в блоге: 3
20.09.2024, 23:53  [ТС]
Цитата Сообщение от HF Посмотреть сообщение
То есть вы точно знаете что будет точно не максимум один элемент
Да, HF, точно знаю.
Цитата Сообщение от HF Посмотреть сообщение
и хотите задержать новых и даже обломать их
Новых не будет. Будет тот же самый, который просто не попал в окно.
Цитата Сообщение от kolorotur Посмотреть сообщение
await нельзя использовать в блоке lock, потому вопрос отпадает
Цитата Сообщение от kolorotur Посмотреть сообщение
В принципе использовать блокирующие операции в асинхронном коде не рекомендуется — нивелирует весь профит асинхронности.
само собой. Это я понимаю и не допускаю мысли блокировать что-то в асинхронном потоке. Нужен лишь "сигнал" от туда.
Цитата Сообщение от kolorotur Посмотреть сообщение
Ну а Slim в имени намекает на легковесность.
Поэтому и "повелся" на него сразу.

Добавлено через 2 минуты
Понимаю, что трудно "опытным" кодом донести толково суть процесса, но одно то, что никто не сказал что-то типа: "фу, ты какое то г***но понаписал" - уже результат
Значит, правильной дорогой иду..

Добавлено через 3 минуты
Цитата Сообщение от kolorotur Посмотреть сообщение
Можете использовать SemaphoreSlim
Семафор был бы полезен, как мне кажется, в случае многопоточного доступа. Я рассматривал его возможности.
Но пока вроде бы ManualResetEventSlim решил проблему. Я даже AutoResetEvent прикидывал - тоже ничего.

Добавлено через 16 минут
HF, очередь (стек) понятно дело, приходят на ум, но тут сразу же возникает зависимость от времени цикла самого сервиса:
- если очередь пуста, выполнить свой запрос
- если очередь не пуста, выполнить инструкции из очереди, а затем свой запрос
В любом из этих случаев период запуска будет определяться только временем периода опроса.
А нужно, чтобы любой пришедший запрос выполнился сразу одномоментно, если сервис не занят.
0
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3421 / 2740 / 575
Регистрация: 04.09.2018
Сообщений: 8,608
Записей в блоге: 3
21.09.2024, 16:43  [ТС]
Ну а по итогу все решилось банально:
C#
1
2
3
4
5
6
7
try
{
    lock (lockObj)
    {
        mbSession?.RawIO.Write(ReplaceCommonEscapeSequences(command)); 
    }
}
В данном случае применение ***ResetEvent и уж тем более семафоров оказалось излишним.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
21.09.2024, 17:18
Лучший ответ Сообщение было отмечено wizard41 как решение

Решение

Цитата Сообщение от wizard41 Посмотреть сообщение
Ну а по итогу все решилось банально
А в другом методе как — там, где await?

Ну и на всякий случай: лочить лучше всего непосредственно доступ к общему ресурсу:
C#
1
2
3
4
5
6
try
{
    var escapedCommand = ReplaceCommonEscapeSequences(command);
    lock (lockObj)
        mbSession?.RawIO.Write(escapedCommand); 
}
1
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3421 / 2740 / 575
Регистрация: 04.09.2018
Сообщений: 8,608
Записей в блоге: 3
21.09.2024, 17:35  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
там, где await?
Так это тестовый метод был, там await только для имитации.
Настоящий метод mbSession?.RawIO.Write не асинхронный, более того, из него не прокинуты ексепшены "наверх", т.е. в нем тоже крутится какой-нибудь Task<void> внутри.
1
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3421 / 2740 / 575
Регистрация: 04.09.2018
Сообщений: 8,608
Записей в блоге: 3
21.09.2024, 18:46  [ТС]
10k+ итераций и ни одной "коллизии", думаю, вопрос с этим решен.

Раньше, на 200-300 итераций одна-две выскакивала стабильно.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
21.09.2024, 18:46
Помогаю со студенческими работами здесь

Проблемы с блокировкой конфигурации в 1С 8
Привет! возникла следующая проблема: при редактировании интерфейсов нет полного доступа на изменение вкладок и т.п., т.к. всё заблоктровано...

Подключение file.tpl в file.tpl
Здравствуйте. Искал ответ на свой вопрос, находил много ответов, но все не то, что надо... В общем в файлах с расширением .php...

Блокировкой сайтов по ключевым словам
Установлена ​​программа ChildWebGuardian на школьном компьютере: требует ключ. Попробовал найти другую прогу, но только бесплатную, и мои...

Чертеж замка с блокировкой ригеля
Из за некоторой сложившиеся ситуации нужно в существующий замок внести конструктивные изменения. В замок планирую поставить доп. миханику...

Написание браузера с блокировкой элементов
Здраствуйте, нужна помошь по написанию своего браузера с возможностями блокировки елементов. (Почти как в адблок) Как ето сделать с...


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

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
Новые блоги и статьи
Символические и жёсткие ссылки в Linux.
algri14 15.03.2026
Существует два типа ссылок — символические и жёсткие. Ссылка в Linux — это дополнительная запись в каталоге, которая может указывать либо на inode «файла-ИСТОЧНИКА», тогда это будет «жёсткая. . .
[Owen Logic] Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ФедосеевПавел 14.03.2026
Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора ВВЕДЕНИЕ Выполняя задание на управление насосной группой заполнения резервуара,. . .
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога Финальные проекты на Си и на C++: hello-sdl3-c. zip hello-sdl3-cpp. zip Результат:
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд. Даже если у вас. . .
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает монорепозиторий в котором находятся все исходники. При создании нового решения, мы просто добавляем нужные проекты и имеем. . .
Модульный подход на примере F#
DevAlt 06.03.2026
В блоге дяди Боба наткнулся на такое определение: В этой книге («Подход, основанный на вариантах использования») Ивар утверждает, что архитектура программного обеспечения — это структуры,. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru