c# - Why is my dynamically valued dictionary iteration ( in parallel ) throwing away my locally scoped variables? -
follow from:
how iterate through dynamically valued dictionary in parallel?
if run function using locally scoped variable, program throws away value - has left me utterly confused.
why happening?
simple example:
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 = 0; foreach (keyvaluepair<string, object> record in dict) { var _recordstored = record; //this is(was) supposed fix woes!!! new system.threading.timer(_ => { i++; //if comment next line, 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)); } (; ; ) { 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)); } } } }
which has output:
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
but ( after ~880) iterations start printing
val: 2 threadid: 10 val: 2 threadid: 12 val: 2 threadid: 10 val: 2 threadid: 12 val: 2 threadid: 10 val: 2 threadid: 12
the wierdest thing is, when remove call processfile
code execute perfectly.
this garbage collection @ work, correctly doing job mind you.
the problem related timers.
before reading further, note trick use locally scoped variable entirely correct. not problem having. continue doing that, if hadn't done have had other/more problems.
ok, so, construct 2 timers, since have 2 values in dictionary, , run "forever", until they're collected.
you're not keeping references timers, garbage collector collect 1 or both of them.
when does, it'll run finalizer of timer before collects it, , stop execution.
the solution hold on timers, or not use timers @ all, let's stay timers, solution easy fix:
int = 0; var timers = new list<system.threading.timer>(); foreach (keyvaluepair<string, object> record in dict) { var _recordstored = record; //this is(was) supposed fix woes!!! timers.add(new system.threading.timer(_ => { ... rest of timer code here }, null, timespan.zero, new timespan(0, 0,0,0,1))); // end parenthesis here } (; ; ) { system.threading.thread.sleep(10000); } gc.keepalive(timers); // prevent list being collected ... // end making timers eligible collection (again)
now: here's snafu trip up. if you're running in debugger, or in debug build, fact it's first value disappears normal, @ same time normal second value never disappear.
why? because variables have had lifetime extended duration of method. in loop, when new system.threading.timer(...)
, compiler silently redefines this:
var temp = `new system.threading.timer(...)`
this variable (temp
) overwritten on each loop iteration, in effect keeping last value assigned it, , timer never collected as long running in debugger or in debug build, since method defined never finishes (the endless loop @ bottom).
the first timer, however, free collected since there no longer keeping reference it.
you can verify this linqpad program:
void main() { (int index = 1; index <= 5; index++) { new noisyobject(index.tostring()); } (;;) { // provoke 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"); } }
when run in linqpad, make sure little button down right in window switched "/o-":
you'll output:
1 constructed 2 constructed 3 constructed 4 constructed 5 constructed 4 finalized 3 finalized 2 finalized 1 finalized
notice how 5 never finalized?
now turn on optimizations clicking "/o-" button , turn "/o+" enable optimizations (release build):
1 constructed 2 constructed 3 constructed 4 constructed 5 constructed 5 finalized 4 finalized 3 finalized 2 finalized 1 finalized
and 5 finalized.
note: said about temporary variable being introduced doesn't happen that, effect same.
if change main method of above linqpad program this:
void main() { new noisyobject("not finalized when /o-"); (;;) { // provoke garbage collector allocatesomememory(); } }
you can verify running under "/o-" never finalized object (well, never such strong word, not in time waited anyway), , under /"o+".
bonus question: if had dropped gc.keepalive(timer);
solution presented above, wouldn't fact have stored in variable change behavior?
not really. problem lifetime of variables. compiler , jitter uses information variable used figure out variables considered "live" @ point in time.
for instance code:
void main() { var obj = new someobject(); callsomemethodthatneverreturns(); }
wouldn't keep obj
alive indefinitely? no. @ point method called, variable no longer used, no code can/will access it, , garbage collector free collect it.
this meant lifetime of variable. in debug build (optimizations off), or when running under debugger, above code looks this:
void main() { var obj = new someobject(); callsomemethodthatneverreturns(); gc.keepalive(obj); }
in essence preventing object being collected. gc.keepalive
no-op method compiler , jitter cannot "optimize away" , seem use object in question, thereby extending lifetime.
this not done in release build (optimizations on), , production program might behave differently under development.
conclusion: testing on same type of build customers get.
Comments
Post a Comment