Передача локальной переменной в другой поток в .net - PullRequest
0 голосов
/ 29 сентября 2018

В следующем коде, почему мы не получаем исключение NullReference и значение var2 равно 56, хотя TestMethod определенно завершился до строки 'Messagebox'?Я прочитал этот отличный ответ от Эрика Липперта и этот пост в блоге, но я до сих пор не понимаю.

void TestMethod()
{
    int var1 = 10;
    List<long> list1 = new List<long>();
    for (int i = 0; i < 5; i++)
        list1.Add(i);

    ThreadPool.QueueUserWorkItem(delegate
    {
        int var2 = var1;
        Thread.Sleep(1000);
        list1.Clear();
        MessageBox.Show(var2.ToString());
    });
    var1 = 56;
}

Ответы [ 2 ]

0 голосов
/ 29 сентября 2018

Я думаю, что вы говорите, что вы ожидаете, что var1 будет освобождено , когда TestMethod() выйдет.В конце концов, локальные переменные хранятся в стеке, и когда метод завершается, указатель стека должен возвращаться в то место, где он был до вызова, что означает, что все локальные переменные освобождены.Если бы это было действительно то, что происходило, var1 не мог бы быть установлен в ноль вообще;он может содержать мусор или биты какой-то другой локальной переменной, созданной позже, когда указатель стека снова перемещается.Это то, что вы имеете в виду?

Для меня загорелся тот факт, что асинхронное мышление вообще не основано на стеке.Стек просто не работает - потому что порядок вызовов не образует стек.Вместо этого биты кода связаны с контекстными объектами, которые хранятся в куче 1009 *.Они могут выполняться в любом порядке и даже одновременно.

Вашему делегату требуется var1, поэтому компилятор переводит его из переменной, хранящейся в стеке, в переменную, хранящуюся в одном из этих объектов, связанных с поведением делегата.Это то, что называется «закрытием» или «закрытой переменной».Для делегата это выглядит как локальная переменная, потому что она просто больше не находится в стеке.Он будет жить так долго, как этот объект должен жить, даже после выхода TestMethod().

0 голосов
/ 29 сентября 2018

Я думаю, это потому, что делегат сформировал замыкание вокруг переменной var1.Возможно, вам поможет рассмотрение того, как внутреннее закрытие работает.Вы можете обратиться к объяснению здесь

Компилятор (в отличие от среды выполнения) создает другой класс / тип.Функция с вашим замыканием и любые переменные, которые вы закрыли / подняли / перехватили, переписываются по всему вашему коду как члены этого класса.Закрытие в .Net реализовано как один экземпляр этого скрытого класса.

Имея это, я считаю, что примерно сгенерированный компилятором код будет выглядеть так:

void TestMethod()
{
    UnspeackableClosureClass closure = new UnspeackableClosureClass(10);
    List<long> list1 = new List<long>();
    for (int i = 0; i < 5; i++)
        list1.Add(i);

    ThreadPool.QueueUserWorkItem(closure.AutoGeneratedMethod);
    closure.closureVar = 56;
}

public class UnspeackableClosureClass
{
   public int closureVar;
   public UnspeackableClosureClass(int val){closureVar=val}

   public void AutoGeneratedMethod(){
     int var2 = closureVar;
     Thread.Sleep(1000);
     list1.Clear();
     MessageBox.Show(var2.ToString());
  }
}
...