为什么我的动态值字典迭代(并行)丢弃我的本地范围变量?

Ale*_*lex 1 c#

跟进:

如何并行迭代动态值字典?

如果我使用本地范围的变量运行一个函数,我的程序最终会抛弃该值 - 这让我完全糊涂了.

为什么会这样?

简单示例:

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对代码的调用总是会完美执行.

ang*_*son 6

这是工作中的垃圾收集,正确地完成它的工作.

问题与您的计时器有关.

在进一步阅读之前,请注意您使用本地范围变量的技巧是完全正确的.这不是你遇到的问题.所以继续这样做,如果你没有这样做,你会遇到其他/更多的问题.

好吧,所以,你构造了两个计时器,因为你的字典中有两个值,它们"永远"运行,直到它们被收集.

您没有保留对计时器的引用,因此最终垃圾收集器将收集其中一个或两个.

当它发生时,它会在收集计时器之前运行计时器的终结器,这将停止执行.

解决方案是坚持你的计时器,或根本不使用计时器,但让我们留在计时器,解决方案很容易解决:

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-":

LINQPad优化开关

你会得到这个输出:

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构建(优化)中完成的,因此生产程序的行为可能与开发中的不同.

结论:对您的客户将获得的相同类型的构建进行测试.