コレクションを分割してマルチスレッド処理を実行する

foreachのループ処理でコレクション内データを取得し、ループ内でシングルスレッド処理を行うことがあるかと思います。
foreach内でや大量データのバッチ処理や重い処理(APIによるCRUD処理など)を行う場合に、
予めコレクションを分割し、マルチスレッド処理にする方法を記載します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CollectionSplit
{
    class Program
    {
        static void Main(string[] args)
        {
            //0~11000までの連番をListに追加
            var allList = Enumerable.Range(0, 11000).ToList();

            var splitLists = GetSplitList(allList).Select(i => i.ToList()).ToList();

            List<Task> taskList = new List<Task>();

            //分割したコレクションをループし、マルチスレッド処理を実行
            foreach (var splitList in splitLists.Select((item, index) => new { item, index }))
            {
                taskList.Add(Task.Factory.StartNew(() =>
                {
                    foreach (var splitItem in splitList.item)
                    {
                       //実際はここで重い処理(APIによるCRUD処理など)を実行
                        Console.WriteLine($"index: {splitList.index}, value: {splitItem}");
                    }
                },
                TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(taskList.ToArray());

            Console.ReadLine();
        }

        /// <summary>
        /// コレクション分割メソッド
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="allList"></param>
        /// <returns></returns>
        private static IEnumerable<IEnumerable<T>> GetSplitList<T>(List<T> allList)
        {
            //コレクションを分割する単位を指定(この例では16分割する)
            int parallelCount = 16;

            var cntPerTask = allList.Count < parallelCount ? allList.Count : allList.Count / parallelCount;
            var cntMod = allList.Count % parallelCount;

            int count = 0;
            for (int i = 0; i < parallelCount; i++)
            {
                if (i != parallelCount - 1)
                {
                    yield return allList.Skip(count).Take(cntPerTask);
                    count += cntPerTask;
                }
                else
                {
                    //余りは全て最後のコレクションに追加
                    if (cntMod != 0)
                        cntPerTask += cntMod;

                    yield return allList.Skip(count).Take(cntPerTask);
                }
            }
        }
    }
}

参考サイト
ufcpp.net

実行結果

f:id:katharsis1721:20190117233530p:plain