当本地 stackalloc 上的数据时,匿名委托不会在每次迭代中使用新本地
Anonymous delegate not using new local for every iteration when data on local stackalloc
在 C# 中使用匿名 delegate
s 时,CLR 将在堆上为使用过的变量生成局部副本(例如,当前作用域中的变量)。对于当前范围的每个已声明变量,这样的局部变量将被放入堆中。
您可以在此示例中看到此行为:
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());
}
}
该程序的输出是(最后 5 个条目的顺序可能不同,而第一个条目的顺序也可能小于 5。):
* NUM=5
* NUM=5
* NUM=5
* NUM=5
* NUM=5
* NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4
在方法中调用时,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());
}
}
输出:
* NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4
问题是这样的:然而,当把变量赋给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());
}
}
输出:
* NUM=4
* NUM=4
* NUM=4
* NUM=4
* NUM=4
使用常规局部变量时 - 只需替换上面示例中的 call
方法:
static void call(int number)
{
int j = number;
ThreadPool.QueueUserWorkItem(delegate { execute(j); });
}
输出:
* NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4
这种情况让我不相信 C# 中的匿名 delegate
s - 因为我不明白 C# 什么时候不会搞砸我对匿名 delegate
s 的调用。
我的问题:为什么 C# 不跟踪关于匿名 delegate
的 stackalloc
space? 我知道C# 不跟踪。我想知道为什么它不跟踪,如果它使用常规变量的话。
我使用 .NET Core 2.1 和 C# 7.3,包括 /unsafe
用于这些示例的开关。
问题是您正在捕获一个指针。该指针指向由 call
在堆栈上分配的内存 - 指针 保持 对它的引用,即使在方法返回后也是如此,这从根本上说是个坏消息。那时你进入了未定义的领域 - 没有 gua运行tee 稍后会在那个内存中。
每个 stackalloc
确实 分别发生 - 你得到的五个指针都是独立的,但它们 发生 引用同一块内存,因为每个都是单独 stackalloc
执行的结果 当堆栈指针处于相同的值以 开始时。您仍然可以使用该内存,因为它在进程中仍然是有效的内存,但就知道那里会发生什么而言,这样做不是安全的。
ints
变量被 "correctly" 复制到编译器生成的 class 中,但是变量的 value 指的是调用 call
方法时位于堆栈上的内存。当我 运行 代码时,我得到了 "whatever the argument to Thread.Sleep
was. The C# compiler is capturing the variables, which isn't the same as " 捕获堆栈的全部内容的输出。
您不需要完全避免委托 - 您只需要避免将委托与不安全的代码和堆栈分配混在一起。
完全不用匿名函数也能看到这个问题:
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]);
}
}
}
无需使用 any 代理即可轻松查看,但执行与 "stack allocate some memory, and use a pointer to it after that stack has gone away":
相同的操作
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;
}
}
在 C# 中使用匿名 delegate
s 时,CLR 将在堆上为使用过的变量生成局部副本(例如,当前作用域中的变量)。对于当前范围的每个已声明变量,这样的局部变量将被放入堆中。
您可以在此示例中看到此行为:
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());
}
}
该程序的输出是(最后 5 个条目的顺序可能不同,而第一个条目的顺序也可能小于 5。):
* NUM=5
* NUM=5
* NUM=5
* NUM=5
* NUM=5
* NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4
在方法中调用时,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());
}
}
输出:
* NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4
问题是这样的:然而,当把变量赋给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());
}
}
输出:
* NUM=4
* NUM=4
* NUM=4
* NUM=4
* NUM=4
使用常规局部变量时 - 只需替换上面示例中的 call
方法:
static void call(int number)
{
int j = number;
ThreadPool.QueueUserWorkItem(delegate { execute(j); });
}
输出:
* NUM=0
* NUM=1
* NUM=2
* NUM=3
* NUM=4
这种情况让我不相信 C# 中的匿名 delegate
s - 因为我不明白 C# 什么时候不会搞砸我对匿名 delegate
s 的调用。
我的问题:为什么 C# 不跟踪关于匿名 delegate
的 stackalloc
space? 我知道C# 不跟踪。我想知道为什么它不跟踪,如果它使用常规变量的话。
我使用 .NET Core 2.1 和 C# 7.3,包括 /unsafe
用于这些示例的开关。
问题是您正在捕获一个指针。该指针指向由 call
在堆栈上分配的内存 - 指针 保持 对它的引用,即使在方法返回后也是如此,这从根本上说是个坏消息。那时你进入了未定义的领域 - 没有 gua运行tee 稍后会在那个内存中。
每个 stackalloc
确实 分别发生 - 你得到的五个指针都是独立的,但它们 发生 引用同一块内存,因为每个都是单独 stackalloc
执行的结果 当堆栈指针处于相同的值以 开始时。您仍然可以使用该内存,因为它在进程中仍然是有效的内存,但就知道那里会发生什么而言,这样做不是安全的。
ints
变量被 "correctly" 复制到编译器生成的 class 中,但是变量的 value 指的是调用 call
方法时位于堆栈上的内存。当我 运行 代码时,我得到了 "whatever the argument to Thread.Sleep
was. The C# compiler is capturing the variables, which isn't the same as " 捕获堆栈的全部内容的输出。
您不需要完全避免委托 - 您只需要避免将委托与不安全的代码和堆栈分配混在一起。
完全不用匿名函数也能看到这个问题:
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]);
}
}
}
无需使用 any 代理即可轻松查看,但执行与 "stack allocate some memory, and use a pointer to it after that stack has gone away":
相同的操作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;
}
}