. NET synchronous and asynchronous vigilance closure (10),. net
This article continued:. NET synchronization and Asynchronization of atomic operations and spin locks (Interlocked, SpinLock) (9)
At this point, the conventional operations (Common Operations) related to synchronization and Asynchronization have almost been introduced. This article focuses on closures, which may lead to unexpected bugs.
(PS: For the WaitHandle family related essays and final supplements)
I. Alert for closures
Int total = 0; List <Task> taskList = new List <Task> (); for (int I = 0; I <10; I ++) {var task = Task. run () => {System. threading. interlocked. add (ref total, I) ;}); taskList. add (task);} Task. waitAll (taskList. toArray (); PrintInfo (total. toString (); // output result
The demo logic is very simple. It asynchronously accumulates the value of the loop variable I in the loop. Of course, After all asynchronous operations are completed, the accumulative result is output. The result of 1 + 2 + 3... + 9 should be 45.
If you see that there are no problems or problems. You may fall into the trap. This is a common problem in concurrency, and it is also worth noting in this article: Be cautious with closures.
In fact, the output result here is random and should be in [45 ~ 100. If you want to understand why, half of this article is clear.
Ii. Nature of closures
Essentially,A closure is an executable code block, but this code block maintains an additional context (memory), even if a local variable in the Context Environment has exceeded the scope of its original code block, the closure can still be accessed..
/// <Summary> /// view the nature of the closure /// </summary> public void Demo2 () {var func = GetFunc (); PrintInfo ($ "result: {func (). toString ()} "); // The output result is 12} private Func <int> GetFunc () {int result = 10; Func <int> func = () ==>{ result ++; return result ;}; result ++; return func ;}
In the Demo in the preceding example, the partial variable result has the GetFunc method scope. However, when the GetFunc method is executed, the Demo2 method calls the anonymous delegate Func <int>, you can still access the result variable, which is the closure.
Iii. Check whether
Let's use IL to find out exactly what happened in this process. First, let's take a look at the GetFunc method, take this opportunity along with a more in-depth introduction to IL:
. Method private hidebysig instance class [mscorlib] System. Func '1 <int32> GetFunc () cel managed {
// Indicates the size of the allocated stack. During the execution of this method, up to three data items can be stored in the stack at the same time. // The code size is 50 (0x32). maxstack 3
// Declare the four local variables required by the method, such as the [Index] type name
// The index is of the 0 type, which is automatically generated by the compiler. This type has two members, one int result, and the other method with a return value of the int type. The details will be discussed later. . Locals init ([0] class ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0' CS $ <> 8 _ locals0 ', [1] class [mscorlib] System. func '1 int32> func, [2] int32 V_2, [3] class [mscorlib] System. func '1 <int32> V_3)
// A new object, whose type is automatically generated by the compiler, and the reference of the new object will be pushed to the stack IL_0000: newobj instance void ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0 '::. ctor ()
// POP stack, and assign the POP data to the local variable with the index 0. At this time, no data exists in the stack. IL_0005: stloc.0
// Meaningless operation IL_0006: nop
// Push the local variable with the index 0 to the stack IL_0007: ldloc.0
// Push the integer 10 to the stack IL_0008: ldc. i4.s 10
// Two data in the POP stack, and assign the second data to the result field of the first data (reference. Now there is no data in the stack. [The result = 10 value assignment operation is completed] IL_000a: st1_int32 ParallelDemo. Demo. VariableCapturingClass/'<> c _ DisplayClass3_0': result
// Push the local variable with the index 0 to the stack IL_000f: ldloc.0
// Push the unmanaged code pointer corresponding to the method to the stack IL_0010: ldftn instance int32 ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0': '<GetFunc> B _ 0 '()
// Two parameters required by the POP constructor, and a new Func <int> type delegate, and push the new object reference to the stack IL_0016: newobj instance void class [mscorlib] System. func '1 <int32> ::. ctor (object, native int)
// POP stack, and assign the value to the local variable whose index is 1. At this time, no data is in the stack. [new Func <int> operation is completed.] IL_001b: stloc.1
// Push the local variable with the index 0 to the stack IL_001c: ldloc.0
// POP stack, and push the result field value of POP data (reference) to stack IL_001d: ld1_int32 ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0': result
// POP stack, and assign the POP data to the local variable with the index of 2. At this time, no data exists in the stack. IL_0022: stloc.2
// Push the local variable with the index 0 to the stack IL_0023: ldloc.0
// Push the local variable with index 2 to the stack IL_0024: ldloc.2
// Push the number 1to the stack IL_0025: ldc. i4.1
// POP and sum the two data, and push the result to the stack IL_0026: add
// POP data, and assign the value of the second data to the first data (reference) the result field in the stack has no data. [The result ++ operation is completed.] IL_0027: st1_int32 ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0': result
// Push the local variable with index 1 to the stack IL_002c: ldloc.1
// POP stack, and assign the POP data value to the local variable with the index of 3. The stack data is empty. [prepare the tool for returning the value] IL_002d: stloc.3
// Jump to the Code IL_0030 IL_002e: br. s IL_0030
// Push the local variable with index 3 to the stack IL_0030: ldloc.3
// Return IL_0031: ret} // end of method VariableCapturingClass: GetFunc
After reading the above IL code, the only thing you may not quite understand is the type automatically generated by the compiler. Next, let's take a look at the automatically generated type. What exactly is it:
According to, we can determine:
1. The new type is a class.
2. It contains the result field and the type is int32.
3. A <GetFunc> B _ 0 method is included. The returned value is of the int32 type.
Let's take a look at the IL code of the <GetFunc> B _ 0 method:
. Method assembly hidebysig instance int32 '<GetFunc> B _ 0' () cel managed {// code size 28 (0x1c ). maxstack 3. locals init ([0] int32 V_0, [1] int32 V_1) IL_0000: nop
// Push this to reference the stack IL_0001: ldarg.0
// POP stack, and push the result field value of POP data (reference) to the other IL codes of the stack, which will not be explained one by one, because the previous articles have already introduced it. IL_0002: ld1_int32 ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0': result IL_0007: stloc.0 IL_0008: ldarg.0 IL_0009: ldloc.0 IL_000a: ldc. i4.1 IL_000b: add IL_000c: st1_int32 ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0': result IL_0011: ldarg.0 IL_0012: ld1_int32 ParallelDemo. demo. variableCapturingClass/'<> c _ DisplayClass3_0': result IL_0017: stloc.1 IL_0018: br. s IL_001a IL_001a: ldloc.1 IL_001b: ret} // end of method '<> c _ DisplayClass3_0': '<GetFunc> B _ 0'
After reading the IL code, you will find that the method <GetFunc> B _ 0 is actually the method pointed to by anonymously entrusting Func <int>. The context maintained by the closure is actually the result field.
Iv. Review
Finally, let's look back at the first Demo: Alert closure.
In this demo, the cyclic variable I is one of the context environments in the closure (the total is also), because the accumulation is carried out in the Task, when a Task is executed is determined by the scheduler and thread pool, and the execution time of the task is usually slightly delayed, therefore, the cyclic variable I value is accumulated too large, so the result is too large, so the result is a random number [45 ~ 100].
Coming soon, next article: a set of thread security (one article is expected)
Appendix, Demo: http://files.cnblogs.com/files/08shiyan/ParallelDemo.zip
See more: Casual guide: synchronous and asynchronous
(To be continued ...)