HttpClient 에러?

유닛 테스트를 하다가 아래와 같은 예외가 발생했습니다.

Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection’s state is no longer correct.

관련해서 구글에 검색해보니 원인이 명확하지 않은 것 같습니다.
문제가 발생한 유닛테스트 메서드는 아래와 같습니다. NUnit을 통해 작성되었고, 처음보는 클래스는 자체 제작 클래스라고 보시면 될 듯 합니다.


        [TestCase(419364050, EShopeeItemStatus.NORMAL)]
        [TestCase(419364050, EShopeeItemStatus.UNLIST)]
        public async Task InitGmarketWhiteList(long shopId, EShopeeItemStatus eShopeeItemStatus)
        {
            RequestGetItemList req = new()
            {
                ShopId = shopId,
                PartnerId = _shopeeOption.PartnerId,
                Offset = 0,
                PageSize = 100,
                UpdateTimeFrom = new DateTimeOffset(DateTime.Now.AddYears(-3)).ToUnixTimeSeconds(),
                UpdateTimeTo = new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds(),
                ItemStatus = eShopeeItemStatus
            };
            TaskBase<ResponseGetItemBaseInfo> taskBaseRes = await req.FullBaseInfoScanAsync(_shopeeClient);

            if (!taskBaseRes.IsSuccess)
            {
                Assert.False(!taskBaseRes.IsSuccess);
            }

            ResponseGetItemBaseInfo res = taskBaseRes.Result;
            AwesomeSwagClient awesomeSwagClient = new(new HttpClient(), _companyId);
            SemaphoreSlim semaphore = new(20);
            List<Task> tasks = new();

            foreach (ResponseGetItemBaseInfo._Item item in res.ItemList)
            {
                tasks.Add(Task.Run(async () =>
                {
                    await semaphore.WaitAsync();

                    try
                    {
                        Product awesomeSwagSourceItem = await awesomeSwagClient.GetProductFromOrgIdDetailAsync(item.ItemSku);

                        if (awesomeSwagSourceItem is null)
                        {
                            EcWhiteList reqWhiteList = new()
                            {
                                Platform = EPlatform.SHOPEE,
                                PlatformId = item.ItemId.ToString(),
                                ShopId = shopId.ToString(),
                                ShopName = string.Empty,
                            };
                            await _ecWhiteListService.InsertAsync(reqWhiteList); // 예외 발생지점
                        }
                    }
                    finally
                    {
                        semaphore.Release();
                    }
                }));
            }

            await Task.WhenAll(tasks);

            Assert.True(true);
        }

        public async Task InsertAsync(EcWhiteList edata)
        {
            try
            {
                _context.EcWhiteLists.Add(edata);

                await _context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
            }
            
        }

문제가 발생한 메서드는 주석으로 표기했고 메서드의 정의는 아래 InsertAsync 메서드입니다.
Cosmos DB에 Semaphore를 활용하여 웹통신한 결과를 저장하는 코드인데 이런 에러가 발생합니다…
물론 병렬처리없이 동기식으로 foreach문에서 하나씩 돌리면 문제가 발생하지 않습니다.

왜 발생하는 걸까요?

2개의 좋아요

DbContext의 스레딩 문제로 보입니다.

5개의 좋아요

@dimohy 님과 같은 의견입니다.

EntityFramework는 Thread-safety로 디자인되지 않았습니다.

SemaphoreSlim을 생성할때 생성자의 인자로 (1, 1)을 넣어보세요.
아마 해당 장애가 없어질거 같습니다.

4개의 좋아요

아하…그럼 AddRange 형태로 해야겠네요…ㅎㅎ 감사합니다!!

1개의 좋아요

답변 감사드립니다!! 코드 수정해야겠네요 ㅎㅎ

1개의 좋아요

EF의 탈을 쓴, 단순한 컬렉션의 thread-not-safe 문제입니다. 예를 들어, 다음과 같은 식으로도 간단하게 재현을 할 수 있습니다.

static async Task Main(string[] args)
{
    Dictionary<int, int> dict = new Dictionary<int, int>(1000000);
    List<Task> tasks = new List<Task>();

    for (int i = 0; i < 2; i++)
    {
        Task task = Task.Run(() => 
        {
            for (int j = 0; j < 1000000; j++)
            {
                dict[j] = j;
            }
        });
        tasks.Add(task);
    }

    await Task.WhenAll(tasks);
}
6개의 좋아요

예제 코드 감사합니다 ㅎㅎ 동기화가 처리되지 않은 컬렉션 객체에 대해서는 멀티스레딩하면서 수정하면 안된다는 것이군요.

2개의 좋아요