跟进:
如果我使用本地范围的变量运行一个函数,我的程序最终会抛弃该值 - 这让我完全糊涂了.
为什么会这样?
简单示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
Dictionary<string, object> Dict = new Dictionary<string, object>() {
{ "1", new Dictionary<string, object>() {
{"Columns",new object[1]{ "name"} }
}},
{ "2", new Dictionary<string, object>() {
{"Columns",new object[1]{ "name"} }
}}
};
int i = 0;
foreach (KeyValuePair<string, object> record in Dict)
{
var _recordStored = record; //THIS IS(was) SUPPOSED TO FIX MY WOES!!!
new System.Threading.Timer(_ => {
i++;
//if i comment the next line, the code "works" ( albeit doing nothing really" )
processFile((Dictionary<string, object>)_recordStored.Value, new List<object>{});
Console.WriteLine("Val: " + _recordStored.Key + " ThreadID: " + System.Threading.Thread.CurrentThread.ManagedThreadId+ " "+ i);
}, null, TimeSpan.Zero, new TimeSpan(0, 0,0,0,1));
}
for (; ; )
{
System.Threading.Thread.Sleep(10000);
}
}
public static void processFile(Dictionary<string, dynamic> record, List<object> fileData)
{
DataTable dt = new DataTable("table");
foreach (string column in record["Columns"])
{
dt.Columns.Add(column, typeof(string));
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
哪个有输出:
Val: 1 ThreadID: 12
Val: 2 ThreadID: 11
Val: 1 ThreadID: 11
Val: 2 ThreadID: 12
Val: 2 ThreadID: 12
Val: 1 ThreadID: 11
Val: 2 ThreadID: 12
Run Code Online (Sandbox Code Playgroud)
但最终(在~880之后)迭代才会开始打印
Val: 2 ThreadID: 10
Val: 2 ThreadID: 12
Val: 2 ThreadID: 10
Val: 2 ThreadID: 12
Val: 2 ThreadID: 10
Val: 2 ThreadID: 12
Run Code Online (Sandbox Code Playgroud)
关于这一切最奇怪的是,当我删除processFile对代码的调用总是会完美执行.
这是工作中的垃圾收集,正确地完成它的工作.
问题与您的计时器有关.
在进一步阅读之前,请注意您使用本地范围变量的技巧是完全正确的.这不是你遇到的问题.所以继续这样做,如果你没有这样做,你会遇到其他/更多的问题.
好吧,所以,你构造了两个计时器,因为你的字典中有两个值,它们"永远"运行,直到它们被收集.
您没有保留对计时器的引用,因此最终垃圾收集器将收集其中一个或两个.
当它发生时,它会在收集计时器之前运行计时器的终结器,这将停止执行.
解决方案是坚持你的计时器,或根本不使用计时器,但让我们留在计时器,解决方案很容易解决:
int i = 0;
var timers = new List<System.Threading.Timer>();
foreach (KeyValuePair<string, object> record in Dict)
{
var _recordStored = record; //THIS IS(was) SUPPOSED TO FIX MY WOES!!!
timers.Add(new System.Threading.Timer(_ => {
... rest of your timer code here
}, null, TimeSpan.Zero, new TimeSpan(0, 0,0,0,1))); // extra end parenthesis here
}
for (; ; )
{
System.Threading.Thread.Sleep(10000);
}
GC.KeepAlive(timers); // prevent the list from being collected which would ...
// end up making the timers eligible for collection (again)
Run Code Online (Sandbox Code Playgroud)
现在:这是一个让你吵架的蠢货.如果你在调试器或DEBUG构建中运行它,那么它是第一个消失的值是完全正常的,同时第二个值永远不会消失是完全正常的.
为什么?因为所有变量的生命周期都延长到了方法的持续时间.在循环中,当你简单地说new System.Threading.Timer(...),编译器默默地将其重新定义为:
var temp = `new System.Threading.Timer(...)`
Run Code Online (Sandbox Code Playgroud)
这个变量(temp)在每次循环迭代时被覆盖,实际上保留了分配给它的最后一个值,只要你在调试器或DEBUG构建中运行它,就永远不会收集这个定时器,因为定义它的方法永远不会完成(底部的无限循环).
但是,第一个计时器可以自由收集,因为不再有任何保留对它的引用.
您可以使用此LINQPad程序验证这一点:
void Main()
{
for (int index = 1; index <= 5; index++)
{
new NoisyObject(index.ToString());
}
for (;;)
{
// do something that will provoke the garbage collector
AllocateSomeMemory();
}
}
private static void AllocateSomeMemory()
{
GC.KeepAlive(new byte[1024]);
}
public class NoisyObject
{
private readonly string _Name;
public NoisyObject(string name)
{
_Name = name;
Debug.WriteLine(name + " constructed");
}
~NoisyObject()
{
Debug.WriteLine(_Name + " finalized");
}
}
Run Code Online (Sandbox Code Playgroud)
在LINQPad中运行此命令时,请确保窗口右侧的小按钮切换为"/ o-":

你会得到这个输出:
1 constructed
2 constructed
3 constructed
4 constructed
5 constructed
4 finalized
3 finalized
2 finalized
1 finalized
Run Code Online (Sandbox Code Playgroud)
请注意5是如何永远定型的?
现在通过单击"/ o-"按钮打开优化并将其转换为"/ o +"以启用优化(RELEASE构建):
1 constructed
2 constructed
3 constructed
4 constructed
5 constructed
5 finalized
4 finalized
3 finalized
2 finalized
1 finalized
Run Code Online (Sandbox Code Playgroud)
现在5也完成了.
注意:我所说的关于引入临时变量的内容实际上并不是这样,但效果是一样的.
如果您将上述LINQPad程序的main方法更改为:
void Main()
{
new NoisyObject("Not finalized when /o-");
for (;;)
{
// do something that will provoke the garbage collector
AllocateSomeMemory();
}
}
Run Code Online (Sandbox Code Playgroud)
你可以验证在"/ o-"下运行它永远不会最终确定对象(好吧,从来没有这么强的词,不管我还在等待它的时候),它会在/"o +"之下.
额外的问题:如果你GC.KeepAlive(timer);从我上面提到的解决方案中删除了,那么我将它存储在变量中的事实是否会改变行为?
并不是的.问题是变量的生命周期.编译器和JITter使用有关变量用于确定哪些变量在任何时间点被视为"实时"的信息.
例如这段代码:
void Main()
{
var obj = new SomeObject();
CallSomeMethodThatNeverReturns();
}
Run Code Online (Sandbox Code Playgroud)
这不会obj无限期地保持活力吗?在调用方法时,不再使用该变量,没有代码可以/将访问它,因此垃圾收集器可以自由地收集它.
这就是我对变量生命周期的意义.在DEBUG构建(优化关闭)或在调试器下运行时,上面的代码如下所示:
void Main()
{
var obj = new SomeObject();
CallSomeMethodThatNeverReturns();
GC.KeepAlive(obj);
}
Run Code Online (Sandbox Code Playgroud)
实质上是防止收集对象.GC.KeepAlive本质上是一种无操作方法,编译器和JITter无法"优化",因此似乎使用了相关对象,从而延长了它的生命周期.
这不是在RELEASE构建(优化)中完成的,因此生产程序的行为可能与开发中的不同.
结论:对您的客户将获得的相同类型的构建进行测试.