다음은 BlockingCollection
많은 누락 된 기능과 함께 대기를 지원 하는의 매우 기본적인 구현입니다 . AsyncEnumerable
8.0 이전의 C # 버전에 대해 비동기 열거를 가능하게 하는 라이브러리를 사용합니다 .
public class AsyncBlockingCollection<T>
{
private Queue<T> _queue = new Queue<T>();
private SemaphoreSlim _semaphore = new SemaphoreSlim(0);
private int _consumersCount = 0;
private bool _isAddingCompleted;
public void Add(T item)
{
lock (_queue)
{
if (_isAddingCompleted) throw new InvalidOperationException();
_queue.Enqueue(item);
}
_semaphore.Release();
}
public void CompleteAdding()
{
lock (_queue)
{
if (_isAddingCompleted) return;
_isAddingCompleted = true;
if (_consumersCount > 0) _semaphore.Release(_consumersCount);
}
}
public IAsyncEnumerable<T> GetConsumingEnumerable()
{
lock (_queue) _consumersCount++;
return new AsyncEnumerable<T>(async yield =>
{
while (true)
{
lock (_queue)
{
if (_queue.Count == 0 && _isAddingCompleted) break;
}
await _semaphore.WaitAsync();
bool hasItem;
T item = default;
lock (_queue)
{
hasItem = _queue.Count > 0;
if (hasItem) item = _queue.Dequeue();
}
if (hasItem) await yield.ReturnAsync(item);
}
});
}
}
사용 예 :
var abc = new AsyncBlockingCollection<int>();
var producer = Task.Run(async () =>
{
for (int i = 1; i <= 10; i++)
{
await Task.Delay(100);
abc.Add(i);
}
abc.CompleteAdding();
});
var consumer = Task.Run(async () =>
{
await abc.GetConsumingEnumerable().ForEachAsync(async item =>
{
await Task.Delay(200);
await Console.Out.WriteAsync(item + " ");
});
});
await Task.WhenAll(producer, consumer);
산출:
12 34 5678 9 10
업데이트 : C # 8이 출시되면서 비동기 열거 가 기본 제공 언어 기능이되었습니다. 필수 클래스 ( IAsyncEnumerable
, IAsyncEnumerator
)는 .NET Core 3.0에 포함되며 .NET Framework 4.6.1+ ( Microsoft.Bcl.AsyncInterfaces ) 용 패키지로 제공됩니다 .
다음은 GetConsumingEnumerable
새로운 C # 8 구문을 특징으로 하는 대체 구현입니다.
public async IAsyncEnumerable<T> GetConsumingEnumerable()
{
lock (_queue) _consumersCount++;
while (true)
{
lock (_queue)
{
if (_queue.Count == 0 && _isAddingCompleted) break;
}
await _semaphore.WaitAsync();
bool hasItem;
T item = default;
lock (_queue)
{
hasItem = _queue.Count > 0;
if (hasItem) item = _queue.Dequeue();
}
if (hasItem) yield return item;
}
}
동일한 방법 으로 await
및 의 공존에 유의하십시오 yield
.
사용 예 (C # 8) :
var consumer = Task.Run(async () =>
{
await foreach (var item in abc.GetConsumingEnumerable())
{
await Task.Delay(200);
await Console.Out.WriteAsync(item + " ");
}
});
메모 await
전과를 foreach
.
await Task.Run(() => blockingCollection.Take())
작업 을 사용 하면 작업이 다른 스레드에서 수행되고 UI 스레드가 차단되지 않습니다.