Инкремент глобальной переменой из таска

Как правильно увеличивать глобальную переменную внутри таска ?

А то получается что использую одни и те же числа по несколько раз(

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        List<int> listChisel = new List<int>();
        int counter = 0;

        private void Button_Click(object sender, RoutedEventArgs e)
        {

            for (int i = 0; i < 100; i++)
            {
                listChisel.Add(i);
            }

            ZapuskPotokov(100);
        }


        private void ZapuskPotokov(int viborKolPotokov)
        {
            for (int i = 0; i < viborKolPotokov; i++)
            {
                Task.Run(() => Algoritm());
            }
        }

        private void Algoritm()
        {
            Debug.WriteLine(listChisel[counter]);
            counter++;
        }
    }
}

Ожидаю например: 195246837... - где все числа уникальны

Получаю: 000433221..


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

Автор решения: aepot

Потому что переменная counter не потокобезопасна. 4 потока ее читают, потом 2 пишут, потом 3 читают, вразнобой. Чтобы исправить это, нужно сделать так, чтобы читать и писать в переменную мог одновременно только один поток. При чем между чтением и записью в одном потоке, другой поток не мог её ни читать, ни писать.

private object _locker = new object();

private void Algoritm()
{
    int value;
    lock (_locker)
    {
        value = listChisel[counter++];
    }
    Debug.WriteLine(value);
}

Или так, с учетом, что список ни кем не изменяется, пока работают потоки.

private object _locker = new object();

private void Algoritm()
{
    int index;
    lock (_locker)
    {
        index = counter++;
    }
    Debug.WriteLine(listChisel[index]);
}

Или даже так, с помощью атомарного инкремента

private void Algoritm()
{
    int index = Interlocked.Increment(ref counter) - 1;
    Debug.WriteLine(listChisel[index]);
}

В документации Interlocked.Increment ошибка в переводе на русский.

Возвращаемое значение

Int32 Увеличиваемое значение.

Тогда как в английском

Returns

Int32 The incremented value.

Что в переводе означает "увеличенное значение", а не увеличиваемое.

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

lock add dword ptr [rcx], 1

Где регистр rcx содержит адрес ref counter в памяти.

→ Ссылка