flareduser Ответов: 1

Почему один и тот же аргумент передается методу при вызове через поток?


Ну, я немного новичок в концепции нитей. Я нахожусь на начальной стадии экспериментов с ним. Я написал следующий код, чтобы посмотреть, как будут выполняться потоки, помещенные в список.

using System;
using System.Threading;
using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static object obj = new object();
        static void Main(string[] args)
        {
            List<Thread> threadList = new List<Thread>();
            for (int i = 0; i < 10; i++)
            {
                threadList.Add(new Thread(() => ExecuteThread($"{ i }")));
            }

            foreach (var thread in threadList)
            {
                thread.Start();
            }
            return;
        }

        public static void ExecuteThread(string name)
        {

            Console.WriteLine($"In thread {name}");
            lock (obj)
            {
                Console.WriteLine($"Inside the lock of thread {name}.");
                Console.WriteLine("Waiting for keypress....");
                Console.ReadKey();
            }

            return;
        }
    }
}


И я получил следующий результат :

В теме 10
В теме 10
В теме 10
В теме 10
В теме 10
В теме 10
В теме 10
В теме 10
В теме 10
В теме 10
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....
Внутри замка резьба 10.
Жду нажатия клавиши....

-------

Почему он показывает "внутри замка нити 10", а не "внутри замка нити i" ? Какую особенность потоков я не понимаю с этим выводом ?

(Под "i" я подразумеваю значение i, указанное в цикле.)

Что я уже пробовал:

Я попытался найти мгновенные значения каждой переменной с помощью отладчика, но это не очень помогло здесь.

1 Ответов

Рейтинг:
11

Richard Deeming

Делегат, которого вы передаете в Thread конструктор захватывает локальную переменную. При создании делегата в for цикл, каждый делегат имеет общую ссылку на одну и ту же захваченную переменную. Значение этой переменной будет отражать значение захваченной переменной при выполнении делегата, нет значение при создании делегата.

Чтобы избежать этого, вам нужно захватить копию переменной цикла:

for (int i = 0; i < 10; i++)
{
    int iCopy = i;
    threadList.Add(new Thread(() => ExecuteThread($"{iCopy}")));
}
Это обсуждается на StackOverflow:
замыкания - захваченная переменная в цикле в C# - переполнение стека[^]


Richard MacCutchan

Я полагал, что это происходит, но не мог дать надлежащего объяснения. И я все еще не понимаю, как делегат все еще может ссылаться на переменную, поскольку она существует только в цикле for.

Richard Deeming

Переменная передается компилятором в поле класса замыкания.
Неочевидность использования замыканий C# [^]

Richard MacCutchan

Да, я перешел по этой ссылке на SO и прочитал объяснение. А теперь мне нужно прилечь.

flareduser

Просто увидел ссылку, на которую вы ссылались. Как действует List<Action> , Если само действие является методом в DisplayClass в Примере for-loop?

Richard Deeming

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

NB: Это может привести к утечке памяти, если у вас есть делегат, указывающий на метод экземпляра класса, и время жизни делегата значительно больше, чем предполагаемое время жизни класса.

Делегаты - Руководство По Программированию На C# | Microsoft Docs[^]
Открыть делегатов и Закрытые Делегаты – SLaks.Blog[^]

flareduser

Спасибо.