다음의 에코 서버 코드보다 더 간결하게 C#으로 작성할 수 있을까요?

아래의 코드는 C#으로 간단히 작성해본 에코 서버입니다. 전송한 텍스트를 그대로 반환해주는데요, 이 코드보다 더 간결하게 작성하는 방법이 있을까요?

※ Accept부터 모든 네트워크 I/O를 비동기로 구성해서 스레드풀에서 사용하는 스레드는 연결 갯수에 따라 증가하지 않아야 합니다.

using System.Net.Sockets;
 
var ts = TcpListener.Create(6000);
ts.Start();
 
_ = Task.Run(async () =>
{
    while (true)
    {
        var client = await ts.AcceptTcpClientAsync();
        var s = client.GetStream();
        _ = Task.Run(async () =>
        {
            using var br = new StreamReader(s);
            using var bw = new StreamWriter(s);
 
            while (true)
            {
                var text = await br.ReadLineAsync();
                if (text is null)
                    break;
                text = text.Trim();
               
                Console.WriteLine($"{ThreadPool.ThreadCount}: {text}");
                await bw.WriteLineAsync(text);
                await bw.FlushAsync();
            }
        });
    }
});
 
Console.ReadLine();
ts.Stop();
3개의 좋아요

AcceptTcpClientAsync 에서 기다리고 있다가 새로운 클라이언트가 들어오면 매번 task를 생성하는 것처럼 보이는데 클라이언트에서 끊지 않고 계속 연결되면 스레드가 늘어나지는 않는걸까요?

3개의 좋아요

네. 늘어나지 않습니다.

3개의 좋아요

이 코드보다 더 간결한게 있는지 없는지 그런건 사실 관심이 없고,
오로지 스레드 카운트가 늘어나지 않는다는 사실에 신기할 따름입니다 :scream:
좋은 가르침 감사합니다…

1개의 좋아요

뭐… 사실 이정도 예제에서 어지간한 스트레스로 스레드가 늘지는 않겠지만
혹시, @dimohy 님의 답변에서

무지성으루 Task 사용해도 스레드는 안 늘어날거야

하는 생각을 할 수도 있어서 살짝 깔짝대 보자면요…

별도의 SynchronizationContext 를 설정해 스레드를 직접 핸들링 하지 않았다면
Task 사용 역시 스레드풀에서 동작합니다.
(특히 Task.Run() 메서드는 직접 스레드 풀에 작업을 할당하는 기능이죠.)

스레드풀의 최소 스레드는 cpu 개수(논리 코어포함)로 기본설정되어 있어요.
이 개수의 스레드가 풀에서 기본적으로 재사용되는데
작업 큐 할당이 여유 스레드 개수를 넘어서면 약간의 지연 후 스레드가 늘어나게 됩니다.
(일해라 스레드!)

Task 의 수행 역시 스레드풀에서 이루어지기 때문에
스레드풀의 여유 스레드가 부족할 정도로 Task 가 누적되면
스레드풀에서 점진적으로 스레드를 추가 생성하게됩니다.
(당연히 이 때에도 스레드가 추가될 때 지연이 발생합죠 -ㅁ-!)

Task 를 사용해도 스레드풀의 스레드가 늘어날 수 있다는 얘기예요. ㅇㅅㅇ!

뭐 지금 예제에서는 워낙 빠르게 Task 가 종료되어
여유 min thread 가 계속 유지될 거니까 스레드가 크게 늘어날 일은 없지만

혹여나 각 Task 처리 구간에서 시간이 많이 걸리는 작업이 늘어나고
그 작업들이 전반적으로 쌓여서 전체 스레드 풀의 작업 큐가 일정 수준 이상으로 누적되면
스레드가 늘어나는 건 사실임다.

그래서 혹여나 반응성에 문제가 생길 수 있으니, 라이브 상황에서는
테스트를 거친 후 min thread 의 개수를 넉넉히 설정하는 작업이 필요하겠죵?

네… 뭐 그냥 그렇다구요.

4개의 좋아요

아 그랬군요…
@dimohy 님은 데이터 수신이 쉴새없이 이어지는 환경이 아니라,
그때그때 단발성으로 수신된다면 스레드풀에 있던 task가 동시에 동작할 필요가 없어지고, 여유가 생기니까 하나의 스레드로 처리가 가능하다는걸 보여주신거 같아요.
(아닌가…ㅡ,.ㅡ)
@Greg.Lee 님의 조언도 감사합니다.^^

3개의 좋아요

저같은 경우 스레드 활용을 최대화시키기 위해서
아래 코드를 사용하곤 합니다.
최소 스레드를 강제 지정하는 코드입니다.

// Get thread pool information
int workerThreadsMin, completionPortThreadsMin;
ThreadPool.GetMinThreads(out workerThreadsMin, out completionPortThreadsMin);
int workerThreadsMax, completionPortThreadsMax;
ThreadPool.GetMaxThreads(out workerThreadsMax, out completionPortThreadsMax);
// Adjust min threads
ThreadPool.SetMinThreads(workerThreadsMax, completionPortThreadsMin);
3개의 좋아요