后续行动:

How do I iterate through a dynamically valued dictionary in parallel?

如果我使用局部作用域的变量运行函数,则我的程序最终只会丢弃该值-这使我完全困惑。

为什么会这样呢?

简单的例子:

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));
            }
        }
    }
}


具有以下输出:

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


但是最终(约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


关于这一切的最奇怪的事情是,当我删除对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)


现在:这是一条让您绊倒的小食。如果在调试器或DEBUG构建中运行此代码,则它是第一个消失的值是完全正常的,同时,第二个值永不消失是完全正常的。

为什么?因为所有变量的寿命都延长到该方法的持续时间。在循环中,当您简单地说new System.Threading.Timer(...)时,编译器会无声地将其重新定义为:

var temp = `new System.Threading.Timer(...)`


该变量(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");
    }
}


在LINQPad中运行此命令时,请确保将窗口右侧向下的小按钮切换为“ / o-”:



您将获得以下输出:

1 constructed
2 constructed
3 constructed
4 constructed
5 constructed
4 finalized
3 finalized
2 finalized
1 finalized


注意5永远不会完成吗?

现在,通过单击该“ / o-”按钮打开优化,并将其转到“ / o +”以启用优化(RELEASE构建):

1 constructed
2 constructed
3 constructed
4 constructed
5 constructed
5 finalized
4 finalized
3 finalized
2 finalized
1 finalized


现在5也已完成。

注意:我所说的关于引入临时变量的事情并不是真的那样,但是效果是一样的。

如果将上面的LINQPad程序的主要方法更改为此:

void Main()
{
    new NoisyObject("Not finalized when /o-");
    for (;;)
    {
        // do something that will provoke the garbage collector
        AllocateSomeMemory();
    }
}


您可以验证在“ / o-”下运行它永远不会完成该对象(嗯,从来没有这么强的词,无论如何我都没有等待它),它将在/“ o +”下。

额外的问题:如果您从上面介绍的解决方案中删除了GC.KeepAlive(timer);,那么将其存储在变量中的事实是否会改变行为?

并不是的。问题是变量的生存期。编译器和JITter使用有关在何处使用变量的信息来确定哪些变量在任何时间点都被视为“实时”。

例如此代码:

void Main()
{
    var obj = new SomeObject();
    CallSomeMethodThatNeverReturns();
}


这不会使obj无限期地存活吗?否。在调用该方法时,不再使用该变量,没有代码可以/将无法访问该变量,因此垃圾收集器可以自由地收集它。

这就是我关于变量生存期的意思。在DEBUG构建(优化关闭)中,或在调试器下运行时,以上代码如下所示:

void Main()
{
    var obj = new SomeObject();
    CallSomeMethodThatNeverReturns();
    GC.KeepAlive(obj);
}


本质上是防止物体被收集。 GC.KeepAlive本质上是一种无操作方法,编译器和JITter无法“优化”这种方法,因此似乎会使用所讨论的对象,从而延长其寿命。

这不是在RELEASE构建(优化处于启用状态)中完成的,因此生产程序的行为可能与开发过程有所不同。

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

关于c# - 为什么动态值字典迭代(并行)会丢弃局部范围的变量?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/17662400/

10-11 22:52
查看更多