C#에서 async/await를 사용할 때 lock을 함께 쓰려고 하면 에러가 난다.
이때 어떻게 하면 lock을 쓸 수 있을까? 🤯
🔐 전통적인 lock과 비동기 코드의 문제점
lock문은 동기(synchronous)코드에서만 사용 가능하다.
비동기 코드에서 await를 만나면 스레드가 컨텍스트를 해제하면서 락을 제대로 해제하지 못하는 상황이 생길 수 있기 때문이다.
private static readonly object _lock = new object();
public async Task DoSomethingAsync()
{
lock (_lock) // 비동기 코드에서는 사용 불가!
{
await Task.Delay(1000);
}
}
결과? 👉 에러! The 'await' operator cannot be used in a 'lock' statement. 😭 |
🚦 해결 방법: SemaphoreSlim 사용하기!
비동기 환경에서는 ** SemaphoreSlim **을 쓰면 깔끔하게 해결 가능하다.
전통적인 락 대신 WaitAsync()와 Release()로 락을 관리하는 방식이다.
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public async Task DoSomethingAsync()
{
await _semaphore.WaitAsync(); // 락 획득
try
{
Console.WriteLine("작업 시작!");
await Task.Delay(1000); // 어떤 비동기 작업
Console.WriteLine("작업 완료!");
}
finally
{
_semaphore.Release(); // 락 해제
}
}
이렇게 하면 데드락 없이 안전하게 비동기 코드에서도 락을 걸 수 있다.
🛠️ 더 깔끔한 방법: AsyncLock 클래스 만들기!
여러 곳에서 락을 쓰려면, SemaphoreSlim을 직접 쓰는 것보다
AsyncLock 클래스를 만들어서 관리하는 게 더 편리하다.
AsyncLock
public sealed class AsyncLock
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public async Task<IDisposable> LockAsync()
{
await _semaphore.WaitAsync();
return new Releaser(_semaphore);
}
private sealed class Releaser : IDisposable
{
private readonly SemaphoreSlim _semaphore;
public Releaser(SemaphoreSlim semaphore)
{
_semaphore = semaphore;
}
public void Dispose()
{
_semaphore.Release();
}
}
}
사용 예제
private static readonly AsyncLock _lock = new AsyncLock();
public async Task DoSomethingAsync()
{
using (await _lock.LockAsync()) // 락 자동 관리
{
Console.WriteLine("임계 영역 진입!");
await Task.Delay(1000);
Console.WriteLine("임계 영역 종료!");
}
}
요약 정리
- 전통적 lock → 비동기에서 사용 ❌
- SemaphoreSlim → 비동기 락 가능! ✅
- AsyncLock → 재사용성 높고 깔끔하게 락 관리! ✨
[참고]
https://jacking75.github.io/csharp_asyncawait_lock/
C# - async-await에서 lock 사용하기 - jacking75
출처 C#에서 비동기 메소드에서는 lock을 쓸 수 없다. 이 글은 그래도 lock을 사용 하고 싶을 때를 위한 것이다. lock이 필요한 경우를 예를 들면 아래처럼 더블 체크 락킹을 하고 싶을 때이다. // /이
jacking75.github.io
https://medium.com/@tyschenk20/mixing-traditional-locks-with-async-code-in-c-27431f857e01