День 1390. #ЗаметкиНаПолях #AsyncTips
Блокировки с async
Задача
Имеются общие данные. Требуется обеспечить безопасное чтение и запись этих данных из разных блоков кода, внутри которых может использоваться await.
Решение
Тип SemaphoreSlim был обновлён в .NET 4.5 для обеспечения совместимости с async. Пример использования:
class MyClass
{
private int _value;
// Блокировка защищает поле _value
private readonly SemaphoreSlim
semaphore = new SemaphoreSlim(1);
public async Task IncAsync(string name)
{
await semaphore.WaitAsync();
try
{
int oldValue = _value;
await Task.Delay(1000 * oldValue);
_value = oldValue + 1;
Console.WriteLine($"{name}: {_value}");
}
finally
{
semaphore.Release();
}
}
}
Тестовый вариант использования класса:
var cls = new MyClass();
for (int i = 1; i <= 5; i++)
{
int count = i;
Thread t = new(
async () =>
await cls.IncAsync("Поток " + count)
);
t.Start();
}
Console.ReadLine();
Этот код будет выводить одну за другой строки с постоянно увеличивающейся задержкой между ними:
Поток 4: 1
Поток 3: 2
Поток 2: 3
Поток 5: 4
Поток 1: 5
Заметьте, что SemaphoreSlim принимает в качестве параметра конструктора количество потоков, которые одновременно могут получить доступ к блокировке. В данном случае мы задали 1.
В этой ситуации действуют рекомендации из предыдущего совета:
Экземпляры блокировок должны быть приватными; т.е. не должны быть доступными за пределами класса. Обязательно чётко документируйте (и тщательно продумывайте), что именно защищает экземпляр блокировки. Сведите к минимуму объём кода, выполняемого при удержании блокировки. В частности, не вызывайте произвольный код, включая выдачу событий, вызов виртуальных методов и вызов делегатов.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 12.