Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.80/5: Рейтинг темы: голосов - 5, средняя оценка - 4.80
0 / 0 / 0
Регистрация: 05.06.2019
Сообщений: 27

Порядок доступа задач (Task) к блокированному объекту (lock)

06.07.2022, 22:00. Показов 1162. Ответов 18
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Асинхронный канал (System.Threading.Channels) постоянно выстреливает разные задачи (Task), которые выполняются автономно, но те объекты коллекции, с которыми взаимодействует задача обернуты в lock.

Все задачи выстреливаются поочередно, но ни одна не дожидается выполнения предыдущей.

Канал обрабатывает огромное количество сообщений и у меня возник вопрос: будут ли эти задачи выполнены в том порядке, в котором они были созданы?

Пример:
1) запускается Задача № 1 и блокирует lock.
2) запускается Задача № 2 и встает в очередь, ожидая открытия lock.
3) запускается Задача № 3 и встает в очередь, ожидая открытия lock.
4) завершается Задача № 1 и разблокирует lock.
5) может ли первым зайти в изменяемый объект не Задача № 2, а Задача № 3? И только потом Задача № 2? И как этого избежать? Например, ожидая (await) выполнения задачи #1 и только после этого запускать следующую?

Пример кода:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
while (await _reader.WaitToReadAsync(_cts.Token).ConfigureAwait(false))
{
     object obj = await _reader.ReadAsync().ConfigureAwait(false);
     _ = Task.Run(() => _collection.UpdateCollection(obj)).ConfigureAwait(false);
}
 
void UpdateCollection(object obj)
{
     lock(_locker)
    {
        ....code...
    }
        
}
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
06.07.2022, 22:00
Ответы с готовыми решениями:

Не как не могу понять код с Task, lock
Доброго всем времени! У меня появились затруднение с пониманием кода, а это в в общем то с lock`ом и Task`ом. Я хотел написать...

Контрол в Task: Вызывающий поток не может получить доступ к данному объекту
На Net 4.5 и WPF есть такой код (при клике на кнопку): private async void but1_Click(object sender, RoutedEventArgs e) { ...

Вывести на экран в символическом виде состояние NUM LOCK, CAPS LOCK и SCROLL LOCK
Помогите решить задачку на турбо си Выводить на экран в символическом виде состояние NUM LOCK, CAPS LOCK и SCROLL LOCK (вкл. / выкл.),...

18
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
07.07.2022, 01:25
Цитата Сообщение от kkkoh Посмотреть сообщение
будут ли эти задачи выполнены в том порядке, в котором они были созданы?
В приведенной реализации — нет.

Цитата Сообщение от kkkoh Посмотреть сообщение
может ли первым зайти в изменяемый объект не Задача № 2, а Задача № 3? И только потом Задача № 2?
Да.

Цитата Сообщение от kkkoh Посмотреть сообщение
как этого избежать?
Обрабатывать задачи поочередно, т.е. в цикле делать await на запускаемой задаче.
По факту у вас именно это и происходит из-за lock, только очередность не гарантируется.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
07.07.2022, 08:44
kkkoh, во-первых, как написал kolorotur, вам стоит обдумать насколько вам действительно нужна здесь асинхронность.

Во-вторых, вы "злоупотребляете" ConfigureAwait.
Если все продолжения должны выполняться не в контексте синхронизации, то заверните всё в один общий таск.
Будет и понятнее и проще в коде.
Так же, вроде, он имеет смысл только для первого await - последующие уже и так будут выполняться не в контексте синхронизации.
Сейчас без компа и проверить не могу. Возможно, kolorotur внесёт ясность.

В-третьих, если нужна очерёдность выполнения тасков, то их можно "цеплять" друг за друга.
Пищу концептуально:
C#
1
2
3
4
Task task = Task.Run(()=>{}); 
 
// Some code
    task = task.ContinueWith(() => _collection.UpdateCollection(obj));
Добавлено через 11 минут
Цитата Сообщение от Элд Хасп Посмотреть сообщение
вам стоит обдумать насколько вам действительно нужна здесь асинхронность.
Проясню.
Из-за await'ов у вас цикл по факту выполняется синхронно.
Описанные вам риски возникают только в случае, если метод _collection.UpdateCollection(obj) выполняется очень долго - значительно дольше чем весь остальной код итерации цикла.
2
0 / 0 / 0
Регистрация: 05.06.2019
Сообщений: 27
07.07.2022, 19:24  [ТС]
К сожалению, асинхронность крайне нужна, так как все взаимосвязано с GUI.
_collection.UpdateCollection(obj) обрабатывает и вправду дольше, чем извлечение объектов из канала.
А что касается task.ContinueWith() - мне кажется, что это точно такое же действие, которое происходит и при использовании async/await.
Про ConfigureAwait читал бегло, показалось, что нужно всегда указывать ручками в максимальном количестве или использовать какие-то нугеты, вроде Fody
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
07.07.2022, 21:42
Цитата Сообщение от kkkoh Посмотреть сообщение
асинхронность крайне нужна, так как все взаимосвязано с GUI.
Вопросы вызывает не сама асинхронность, а то как вы её реализовали.
Покажите полностью весь метод.

Цитата Сообщение от kkkoh Посмотреть сообщение
_collection.UpdateCollection(obj) обрабатывает и вправду дольше, чем извлечение объектов из канала.
Если правильно понял ваши код и проблему, должно быть нечто подобное:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Task task = Task.Run(()=>{}); 
 
await Task.Run(() =>
{
    var token = _cts.Token;
    while (_reader.WaitToRead())
    {
        token.ThrowIfCancellationRequested();
        object obj =  _reader.Read();
        task = task.ContinueWith(() => _collection.UpdateCollection(obj));
    }
});
 
await task; // Для маршалинга исключений, если будут
Добавлено через 46 минут
Цитата Сообщение от kkkoh Посмотреть сообщение
А что касается task.ContinueWith() - мне кажется, что это точно такое же действие, которое происходит и при использовании async/await.
???
Ну, как бы await использует что-то аналогичное ContinueWith, но напрямую их нельзя сравнивать.
ContinueWith - создаёт цепочку последовательных тасков. Что-то аналогичное их очереди.
Вам же нужно было последовательное исполнение UpdateCollection, вот в моём примере и создаются последовательные таски с вызовом этого метода, асинхронные относительно самого цикла, в котором они создаются.
0
0 / 0 / 0
Регистрация: 05.06.2019
Сообщений: 27
12.07.2022, 16:28  [ТС]
Я вас понял.
Конечно, я привел утрированную схему работы.
Коллекция - это усложненный ConcurrencyDictionary<long, Model>.
Когда в нее падает объект, она дает ссылку на конкретную модель, которую я уже локирую.
Более подробно она работает скорее так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
while (await _reader.WaitToReadAsync(_cts.Token).ConfigureAwait(false))
{
     object obj = await _reader.ReadAsync().ConfigureAwait(false);
     _ = Task.Run(() => _collection.UpdateCollection(obj)).ConfigureAwait(false);
}
 
void UpdateCollection(object obj)
{
    
if(obj is Item item)
{
this[item.Id].UpdateModel(item);
}
        
}
 
void UpdateModel(object obj)
{
   lock(_locker)
{
 ... code ...
}
}
А вот по поводу async/await говорят, что это все-таки чистый сахар над ContinueWith: https://stackoverflow.com/ques... ctic-sugar


П.С. в тоге переделал все на глубокое async/await
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
12.07.2022, 17:16
Цитата Сообщение от kkkoh Посмотреть сообщение
Более подробно она работает скорее так:
Опять вы вцепились в ConfigureAwait.
То как вы его используете - лишено смысла.

Добавлено через 13 минут
Цитата Сообщение от kkkoh Посмотреть сообщение
что это все-таки чистый сахар над ContinueWith
Да.
Но это не замена ContinueWith, а некое преобразование асинхронного метода с использованием в том числе ContinueWith.

В грубом приближении:
C#
1
2
3
4
5
6
7
8
9
10
11
12
private async Task MethodAsync()
{
    {
        // Code Block #1
    }
 
    await Task.Run(() => {// Async Code Block); 
 
    {
        // Code Block #2
    }
}
Примерно эквивалентно:
C#
1
2
3
4
5
6
7
8
9
10
private Task Method()
{
    {
        // Code Block #1
    }
 
    Task task =  Task.Run(() => {// Async Code Block); 
 
    return task.ContinueWith(() => {// Code Block #2}); 
}
Но в полной реализации там ещё много нюансов, в том числе с агрегирование и пробрасывание исключений, по разному создаётся стек вызовов и ещё многое другое.

Добавлено через 4 минуты
kkkoh, ContinueWith используется для строго последовательного, но асинхронного, выполнения блоков кода.
И по вашему описанию, вам вроде это и было нужно.
Поэтому я предложил вам явно создать такую цепочку из методов которые должны асинхронно, но последовательно выполняться.
0
 Аватар для IamRain
4694 / 2702 / 734
Регистрация: 02.08.2011
Сообщений: 7,233
12.07.2022, 17:40
Цитата Сообщение от Элд Хасп Посмотреть сообщение
используется для строго последовательного ,но асинхронного, выполнения блоков кода.
await - тоже строго последовательное асинхронное выполнение.
С точки зрения логики - это синхронное выполнение.
С точки зрения потоков, где будет выполняться continuation code - зависит от текущего планировщика задач и наличия/отсутствия контекста синхронизации. И тут как раз всплывает разница между Task.ContinueWith и просто await.

По поводу ConfigureAwait(false) - согласен, тут он лишний, насколько я помню, он затрагивает только поведение для SynchronoizationContext-a. И вообще никаким боком не влияет на поведение TaskScheduler-ов.

Насколько я понял, в итоге самый оптимальный ответ дал, как обычно, kolorotur. => (Правда ответ не был нормально понят)
Цитата Сообщение от kolorotur Посмотреть сообщение
Обрабатывать задачи поочередно, т.е. в цикле делать await на запускаемой задаче.
То есть код TC-а был такой:
C#
1
2
3
4
5
while (await _reader.WaitToReadAsync(_cts.Token).ConfigureAwait(false))
{
     object obj = await _reader.ReadAsync().ConfigureAwait(false);
     _ = Task.Run(() => _collection.UpdateCollection(obj)).ConfigureAwait(false);
}
Соответственно, согласно рекомендации переделать на такой:
C#
1
2
3
4
5
6
while (await _reader.WaitToReadAsync(_cts.Token).ConfigureAwait(false))
{
     object obj = await _reader.ReadAsync();
       // просто делаем синхронный вызов и нафиг удаляем lock-и, если больше нигде не используется
     _collection.UpdateCollection(obj);
}
Элд Хасп, a не вот такие вот финты:
C#
1
2
//*с еврейским акцентом* ну шо это за порнография, Сара? 
Task task = Task.Run(()=>{});
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
12.07.2022, 18:03
Цитата Сообщение от IamRain Посмотреть сообщение
a не вот такие вот финты
А что не так?
Это чтобы избежать проверки на null в каждом цикле.
Или нужно для первого элемента делать отдельный блок кода с инициализацией переменной таском.

Цитата Сообщение от IamRain Посмотреть сообщение
await - тоже строго последовательное асинхронное выполнение.
Ну, так я же об этом же и пишу.
0
 Аватар для IamRain
4694 / 2702 / 734
Регистрация: 02.08.2011
Сообщений: 7,233
12.07.2022, 18:14
Цитата Сообщение от Элд Хасп Посмотреть сообщение
А что не так?
Ну сам подход не выглядит правильным, даже если он рабочий.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
12.07.2022, 19:50
Цитата Сообщение от IamRain Посмотреть сообщение
Ну сам подход не выглядит правильным, даже если он рабочий.
Ну, можно Task.Delay(0).
Чем лучше?

В данном случае, самое оптимальное.
По крайней мере мне не пришло в голову ничего лучше.
Оба других варианта реализации, что я писал выше, будут затратнее и по коду, и по исполнению.
0
 Аватар для IamRain
4694 / 2702 / 734
Регистрация: 02.08.2011
Сообщений: 7,233
12.07.2022, 20:02
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Чем лучше?
Ничем. Наверно просто Task.CompletedTask наиболее подходящий вариант.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
12.07.2022, 20:21
Цитата Сообщение от IamRain Посмотреть сообщение
Наверно просто Task.CompletedTask наиболее подходящий вариант.
Да, лучше.
Никогда не использовал его.
Буду знать.
0
0 / 0 / 0
Регистрация: 05.06.2019
Сообщений: 27
13.07.2022, 11:24  [ТС]
По поводу ConfigureAwait(false).
Не совсем понял, почему он тут лишний?

Ридер работает в отдельном от коллекции потоке. Коллекция обновляет сотни моделей (в том числе в разных потоках, теоретически). При этом и сама коллекция и ее модели связаны с контекстом синхронизации WinForms (он им задан напрямую при создании). И вроде как все ресурсы, которые я читал, говорят, что ConfigureAwait(false) уменьшает время отработки задачи и в целом ее потребление ресурсов.

Потому что каждая проверка ConfigureAwait разворачивается во что-то, наподобие этого:

C#
1
2
3
4
5
6
7
8
9
object scheduler = null;
if (continueOnCapturedContext)
{
    scheduler = SynchronizationContext.Current;
    if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
    {
        scheduler = TaskScheduler.Current;
    }
}
И многие сотни этих проверок в секунду можно сократить.
0
403 / 265 / 69
Регистрация: 12.04.2020
Сообщений: 1,404
13.07.2022, 11:37
Цитата Сообщение от kkkoh Посмотреть сообщение
Не совсем понял, почему он тут лишний?
в книге про GC написано четко и понятно
ставить всегда, если контекст ненужен конечно же
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
13.07.2022, 12:43
Цитата Сообщение от kkkoh Посмотреть сообщение
По поводу ConfigureAwait(false).
Не совсем понял, почему он тут лишний?
1) Он имеет значение ТОЛЬКО, если вызов метода в котором запускается задача происходит в потоке с контекстом синхронизации. По вашим объяснениям, это не относится к вашей задаче.

2) Даже если метод вызывается в потоке с контекстом синхронизации, он имеет значение ТОЛЬКО для кода самого метода, а не для кода методов выполняемых тасками. То есть он не распространяется на вложенные вызовы асинхронных методов (то что у вас ожидают await'ы) и на Task.Run(...). Более точно - все таски выполняются на потоках из пула. Поэтому хоть вы задали ConfigureAwait, хоть не задали - изменить это невозможно. Вот если бы у вас был массивный (тяжёлый, длительный) код вне await и вне тасков, то на его выполнении он теоретически мог бы повлиять. Но у вас нет такого кода.

3) Так как ConfigureAwait(false) принудительно продолжает выполнять код после await в том же потоке в котором выполнялся таск, то есть на пуле потоке, то после await в этом случае уже нет контекста синхронизации. И, соответственно, для следующего await уже даже при ConfigureAwait(true) (значение по умолчанию) всё равно метод продолжит выполнение на пуле потоков. То есть если для первого await задан ConfigureAwait(false), то для последующих задавать его бессмымсленно.

Добавлено через 57 секунд
kolorotur, написал третий пункт....
Но что-то червь сомнения стал грызть.
Всё верно или нет?

Добавлено через 5 минут
4) Задавать его для таска который никто не ожидает _ = Task.Run(() => ...., тоже бессмысленно, поскольку он Настраивает объект типа awaiter, используемый для данного объекта Task.
await - использует эту настройку для задания потока коду который после него.
Можно ещё как-то по иному обрабатывать awaiter после завершения задачи.
Но вы не ожидает завершения задачи и, соответственно, не обрабатываете awaiter.
Так зачем же его тогда настраивать?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
13.07.2022, 15:07
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Всё верно или нет?
Если не планируется продолжать в оригинальном контексте, то ConfigureAwait(false) лучше вызывать при каждом await.
Если асинхронный метод завершился синхронно, то остальной код метода не выносится в продолжение, а отрабатывает дальше как после вызова обычного метода. В этой ситуации контекст синхронизации не меняется и следующий await без вызова ConfigureAwait(false) захватит оригинальный контекст, чего могло не предполагаться во время написания кода.

Пример (запускайте в WinForms или WPF для лучшего эффекта):
C#
1
2
3
4
5
6
7
8
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
 
// Task.Delay(0) завершается синхронно
await Task.Delay(0).ConfigureAwait(false);
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
 
await Task.Delay(1000);
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
Если вы можете гарантировать, что ни один асинхронный метод из цепочки вызовов никогда не завершается синхронно, то можно вызвать ConfigureAwait(false) только на первом await.
Но это гарантия так себе — до первого рефакторинга или обновления пакета, а отловить баг или висяк потом может быть сложно.
1
0 / 0 / 0
Регистрация: 05.06.2019
Сообщений: 27
13.07.2022, 16:22  [ТС]
Я, честно говоря, не понимаю о чем вы.
Тема сложная для полного погружения, но на эти случаи есть рекомендации MSDN.

https://docs.microsoft.com/en-... rogramming

Оттуда:
Use ConfigureAwait(false) when you can
Exceptions: Methods that require con­text
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16140 / 11264 / 2888
Регистрация: 21.04.2018
Сообщений: 33,109
Записей в блоге: 2
13.07.2022, 17:36
kolorotur, понял.
Вызываемые асинхронные методы не обязательно дойдут до создания задачи (таска) и могут быть выполнены полностью синхронно.

А по остальным пунктам я верно написал (в контексте кода из топа темы)?

Добавлено через 2 минуты
Цитата Сообщение от kkkoh Посмотреть сообщение
Оттуда:
Use ConfigureAwait(false) when you can
Exceptions: Methods that require con­text
Всё верно.
Но это не относится к вашему коду.
По крайней мере в той части, что вы его показали.

Добавлено через 3 минуты
kkkoh, для уверено ответа вам, необходимо больше информации.
Покажите полный код всего метода (из которого вы показали строки в топе), пример его вызова - это нужно чтобы понять возможен ли его вызов из потока с контекстом синхронизации.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
13.07.2022, 17:36
Помогаю со студенческими работами здесь

Acer aspire 5552G-N974G64Mikk Проблема с Caps Lock, Num Lock, Scroll Lock
Вообщем у меня такая же проблемма https://www.cyberforum.ru/notebooks/thread552722.html Небольшая проблема на ноутбуке, когда...

Клавиатура мигает всеми тремя индикаторами Caps Lock, Scroll Lock и Num Lock и соответственно не работает!
Здравствуйте парни и девушки! Столкнулся с такой проблемой! Имеется клавиатура Genius Ergomedia 700! Так вот. Начал замечать интересную...

Работа с клавиатурой (клавиши num lock, caps lock, scroll lock)
определить состояние статуса клавиш &quot; num lock, caps lock,scroll lock &quot; с отображением и их измением на экране

диспетчер задач task manager
непонимаю как, но пропал диспетчер напроч, сначала не предавал значения но уже достаточно много вемени прошло и начинает напрягать...

Завершение массива задач Task с использованием CancellationToken
В продолжение темы. https://www.cyberforum.ru/csharp-beginners/thread1299866.html#post6867334 к Backgroundworker это уже не относится. ...


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

Или воспользуйтесь поиском по форуму:
19
Ответ Создать тему
Новые блоги и статьи
Символические и жёсткие ссылки в 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