Работает не так как ожидалось C# Threading

Код готов к запуску. Возвращает не то что ожидается. Ожидается двумерный в первом уровне многомерного. Но работает только с костылём и возвращает немного не то. Я думаю по коду больше видно что я хотел. В отладчике будет неожиданный выход заграницы objArray без костыля.

using System;
using System.Threading;

namespace MyProgram
{
    class Program
    {
        static void Main(string[] args)
        {

            int[] arr = { 4, 5, 6, 2 };
            int[] arr2 = { 4, 5, 6, 2 };

            int n = 2;
            int step = arr.Length / n;

            // Создаем массив для хранения результатов обработки каждой части массива
            
            int[][][] objArray = new int[n][][];// ЕСЛИ ЗАМЕНИТЬ ТУТ n НА n+1 ТО РАБОТАЕТ, НО 
            // НА ВЫХОДЕ ОБЬЕКТ С ТРЕМЯ ЭЛЕМЕНТАМИ ВМЕСТО 2

            // Создаем потоки для каждой части массива и запускаем их параллельно
            Thread[] threads = new Thread[n];
            for (int pIndex = 0; pIndex < n; pIndex++)
            {

                Console.WriteLine(pIndex);
                int startIdx = pIndex * step;
                int endIdx = (pIndex + 1) * step - 1;
                threads[pIndex] = new Thread(() => {
                    // Выполняем операции над текущей частью массива
                    int[][] result = DoSomething();
                    // Сохраняем результат в массив результатов
                    objArray[pIndex] = result; 

                });
                threads[pIndex].Start();
            }

            // Ждем завершения всех потоков
            foreach (Thread thread in threads)
            {
                thread.Join();
            }


            static int[][] DoSomething(){
                int[][] numbers = new int[3][];
                numbers[0] = new int[] { 1, 2, 3 };
                numbers[1] = new int[] { 4, 5, 6, 7 };
                numbers[2] = new int[] { 8, 9 };
                return numbers;
            }


        }
    }
}

Ответы (2 шт):

Автор решения: Павел

Проблема заключается в том, что вы пытаетесь обратиться к элементу массива objArray, который еще не был инициализирован. В данном случае вы создаете массив objArray с размером n, но каждый элемент этого массива должен быть инициализирован как двумерный массив. Однако, вы не инициализируете каждый элемент массива objArray как двумерный массив. Вы можете инициализировать каждый элемент objArray при создании массива, как показано ниже:

int[][][] objArray = new int[n][][];
for (int i = 0; i < n; i++)
{
    objArray[i] = new int[3][];
}

Это создаст массив objArray с n элементами, каждый из которых будет инициализирован как двумерный массив размером 3 на n (т.е. каждый элемент будет представлять собой 3 строки и n столбцов). Затем, внутри цикла, вы можете сохранять результат DoSomething в соответствующий элемент objArray:

threads[pIndex] = new Thread(() => {
    // Выполняем операции над текущей частью массива
    int[][] result = DoSomething();
    // Сохраняем результат в массив результатов
    objArray[pIndex] = result; 
});

Теперь вы можете получить доступ к результатам, сохраненным в objArray, вне цикла потока. Например, вы можете вывести все элементы objArray на консоль следующим образом:

foreach (int[][] array2D in objArray)
{
    foreach (int[] array1D in array2D)
    {
        foreach (int num in array1D)
        {
            Console.Write(num + " ");
        }
        Console.WriteLine();
    }
    Console.WriteLine();
}

UP

        int[] arr = { 4, 5, 6, 2 };
        int[] arr2 = { 4, 5, 6, 2 };

        int n = 2;
        int step = arr.Length / n;

        int[][][] objArray = new int[n][][];

        Thread[] threads = new Thread[n];
        for (int pIndex = 0; pIndex < n; pIndex++)
        {
            int currentIndex = pIndex; // Создаем временную переменную
            Console.WriteLine(pIndex);
            int startIdx = currentIndex * step;
            int endIdx = (currentIndex + 1) * step - 1;
            threads[currentIndex] = new Thread(() => {
                int[][] result = DoSomething();
                objArray[currentIndex] = result; // Используем временную переменную
            });
            threads[currentIndex].Start();
        }

        // Ждем завершения всех потоков
        foreach (Thread thread in threads)
        {
            thread.Join();
        }


        static int[][] DoSomething(){
            int[][] numbers = new int[3][];
            numbers[0] = new int[] { 1, 2, 3 };
            numbers[1] = new int[] { 4, 5, 6, 7 };
            numbers[2] = new int[] { 8, 9 };
            return numbers;
        }

Попробуй так.

→ Ссылка
Автор решения: aepot

Иллюстрация ошибки

for (int i = 0; i < 100; i++)
{
    new Thread(() =>
    {
        Console.WriteLine(i);
    }).Start();
}

Вывод в консоль

2
2
3
4
5
...
90
92
92
92
94
96
97
98
98
100  << вот он, выход за границы массива
100  

Здесь происходит захват значения i в момент запуска потока, а не в момент объявления делегата. Поэтому вывод в консоль будет непредсказуемым, некоторые числа будут повторяться.

Когда поведенине кода в многопоточной среде становится непредсказуемым, это означает что он не является потокобезопасным.

Варианта решения 2

Кешировать переменную локально для каждой итерации цикла

for (int i = 0; i < 100; i++)
{
    int index = i;
    new Thread(() =>
    {
        Console.WriteLine(index);
    }).Start();
}

Использовать AsyncLocal<T>

for (AsyncLocal<int> i = new(); i.Value < 100; i.Value++)
{
    new Thread(() =>
    {
        Console.WriteLine(i.Value);
    }).Start();
}
0
1
2
3
4
5
6
7
8
9
10
...
90
91
92
93
94
95
96
97
98
99
→ Ссылка