Question

I am trying to spawn threads in a for each loop using a copy of the value in a dict.

My initial understanding was that the foreach would create a new scope, and led to:

Dictionary<string, string> Dict = new Dictionary<string, string>() { { "sr1", "1" }, { "sr2", "2" } };
foreach (KeyValuePair<string, string> record in Dict) {
    new System.Threading.Timer(_ =>
    {
        Console.WriteLine(record.Value);
    }, null, TimeSpan.Zero, new TimeSpan(0, 0, 5));
}

which writes

1
2
2
2

instead of (expected):

1
2
1
2

So I tried cloning the kvp in the foreach:

KeyValuePair<string, string> tmp = new KeyValuePair<string, string>(record.Key, record.Value);

but that renders the same result.

I've also tried it with System.Parallel.ForEach but that seems need values that are not dynamic, which is a bit of a train smash for my dictionary.

How can I iterate through my Dictionary with threads?

Était-ce utile?

La solution

The problem is closure over your lambda, the way to fix is is to add a local variable inside the for loop

Dictionary<string, string> Dict = new Dictionary<string, string>() { { "sr1", "1" }, { "sr2", "2" } };
foreach (KeyValuePair<string, string> record in Dict) {

    var localRecord = record;
    new System.Threading.Timer(_ =>
    {
        Console.WriteLine(localRecord.Value);
    }, null, TimeSpan.Zero, new TimeSpan(0, 0, 5));
}

What is happening in your version is it captures the variable record not the value of the variable record. So when the timer runs the 2nd time it uses the "current value" of record which is the 2nd element in the array.

Behind the scenes this is what is kinda what is happening in your version of the code.

public void MainFunc()
{
    Dictionary<string, string> Dict = new Dictionary<string, string>() { { "sr1", "1" }, { "sr2", "2" } };
    foreach (KeyValuePair<string, string> record in Dict) {

        _recordStored = record;
        new System.Threading.Timer(AnnonFunc, null, TimeSpan.Zero, new TimeSpan(0, 0, 5));
    }
}

private KeyValuePair<string, string> _recordStored;

private void AnnonFunc()
{
    Console.WriteLine(_recordStored.Value);
}

See how when your function ran it for the first itteration had the correct version of _recordStored, but after _recordStored got overwritten it will only show the last set value. By creating a local variable it does not do that overwriting.

A way to imagine it (I am not sure how I could represent it in a code example) is it creates _recordStored1 the first loop, _recordStored2 the 2nd loop, and so on. The function uses the correct version of _recordStored# for when it calls the the function.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top