当本地stackalloc上的数据时,匿名委托不为每次迭代使用新的本地

Mat*_*ias 4 c# anonymous-delegates stackalloc threadpool .net-core

delegate在C#中使用匿名s时,CLR将在堆上为已使用的变量生成本地的副本(例如,当前作用域中的变量).对于当前作用域的每个声明变量,这样的local将被放到堆上.

您可以在此示例中看到此行为:

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 5; i++)
            ThreadPool.QueueUserWorkItem(delegate { execute(i); });

        Thread.Sleep(1000);

        Console.WriteLine();

        for (int i = 0; i < 5; i++)
        {
            int j = i;

            ThreadPool.QueueUserWorkItem(delegate { execute(j); });
        }

        Thread.Sleep(1000);
    }

    static void execute(int number)
    {
        Console.WriteLine(" * NUM=" + number.ToString());
    }
}
Run Code Online (Sandbox Code Playgroud)

该程序的输出是(顺序可能在最后5个条目上有所不同,而在第一个条目上也可能得到低于5的数字):

 * NUM=5
 * NUM=5
 * NUM=5
 * NUM=5
 * NUM=5

 * NUM=0
 * NUM=1
 * NUM=2
 * NUM=3
 * NUM=4
Run Code Online (Sandbox Code Playgroud)

在方法中调用时,C#应始终生成本地的新副本.这在此示例中按预期工作:

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 5; i++)
            call(i);

        Thread.Sleep(1000);
    }

    static void call(int number)
    {
        ThreadPool.QueueUserWorkItem(delegate { execute(number); });
    }

    static void execute(int number)
    {
        Console.WriteLine(" * NUM=" + number.ToString());
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

 * NUM=0
 * NUM=1
 * NUM=2
 * NUM=3
 * NUM=4
Run Code Online (Sandbox Code Playgroud)

这是有问题的情况下:但是,它没有分配变量的工作时,stackalloc保留区:

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 5; i++)
            call(i);

        Thread.Sleep(1000);
    }

    static unsafe void call(int number)
    {
        int* ints = stackalloc int[64];

        ints[32] = number;

        ThreadPool.QueueUserWorkItem(delegate { execute(ints[32]); });
    }

    static void execute(int number)
    {
        Console.WriteLine(" * NUM=" + number.ToString());
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

 * NUM=4
 * NUM=4
 * NUM=4
 * NUM=4
 * NUM=4
Run Code Online (Sandbox Code Playgroud)

使用常规局部变量时 - 只需替换call上面示例中的方法:

static void call(int number)
{
    int j = number;

    ThreadPool.QueueUserWorkItem(delegate { execute(j); });
}
Run Code Online (Sandbox Code Playgroud)

输出:

 * NUM=0
 * NUM=1
 * NUM=2
 * NUM=3
 * NUM=4
Run Code Online (Sandbox Code Playgroud)

这种情况让我不相信delegateC#中的匿名者- 因为我不明白C#什么时候不会搞砸我对匿名delegates的调用.

我的问题:为什么C#没有跟踪stackalloc匿名delegates 的空间?我知道C#没有跟踪.我想知道为什么它不跟踪,如果它与常规变量一起.

我使用带有C#7.3的.NET Core 2.1,包括/unsafe那些示例的开关.

Jon*_*eet 6

问题是你正在捕捉一个指针.该指针指的是由堆栈分配的内存call- 并且即使在方法返回后指针仍保持对它的引用,这从根本上是坏消息.那时你正处于未定义的领域 - 不能保证以后在那个记忆中会有什么.

每个都stackalloc 确实是单独发生的 - 你得到的五个指针都是独立的,但是它们碰巧引用同一块内存,因为每个指针都是当堆栈指针处于相同值时单独stackalloc执行的结果.你仍然可以使用的内存,因为它仍然在过程中有效的存储,但它不是安全的知道发生了什么事情在那里的协议来执行.

ints变量被"正确"复制到由编译器生成的类中,但该变量的是指在调用该call方法时堆栈上的内存.当我运行代码时,我输出"无论参数Thread.Sleep是什么.C#编译器正在捕获变量,这与"捕获堆栈的整个内容"不同.

您不需要完全避免代理 - 您只需要避免将代理与不安全的代码和堆栈分配混合在一起.

您可以在不使用任何匿名函数的情况下看到此问题:

using System;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            Call(i);
        }

        Thread.Sleep(999);
    }

    static unsafe void Call(int number)
    {
        Helper helper = new Helper();
        int* tmp = stackalloc int[64];
        helper.ints = tmp;
        helper.ints[32] = number;        
        ThreadPool.QueueUserWorkItem(helper.Method);
    }

    static void Execute(int number)
    {
        Console.WriteLine(" * NUM=" + number.ToString());
    }

    unsafe class Helper
    {
        public int* ints;

        public void Method(object state)            
        {
            Execute(ints[32]);
        }
    }    
}
Run Code Online (Sandbox Code Playgroud)

你可以在不使用任何委托的情况下轻松地看到它,但是做同样的事情"堆栈分配一些内存,并在堆栈消失后使用指向它的指针":

using System;

class Program
{
    unsafe static void Main(string[] args)
    {
        int*[] pointers = new int*[5];
        for (int i = 0; i < 5; i++)
        {
            pointers[i] = Call(i);
        }
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(pointers[i][32]);
        }
    }

    unsafe static int* Call(int number)
    {
        int* ints = stackalloc int[64];
        ints[32] = number;
        return ints;
    }
}
Run Code Online (Sandbox Code Playgroud)