c# - Async CTP and "finally" -


here's code:

static class asyncfinally {     static async task<int> func( int n )     {         try         {             console.writeline( "    func: begin #{0}", n );             await taskex.delay( 100 );             console.writeline( "    func: end #{0}", n );             return 0;         }                 {             console.writeline( "    func: #{0}", n );         }     }      static async task consumer()     {         ( int = 1; <= 2; i++ )         {             console.writeline( "consumer: before await #{0}", );             int u = await func( );             console.writeline( "consumer: after await #{0}", );         }         console.writeline( "consumer: after loop" );     }      public static void asynctest()     {         task t = taskex.runex( consumer );         t.wait();         console.writeline( "after wait" );     } } 

here's output:

consumer: before await #1     func: begin #1     func: end #1 consumer: after await #1 consumer: before await #2     func: begin #2     func: #1     func: end #2 consumer: after await #2 consumer: after loop     func: #2 after wait 

as can see, block executed much later you'd expect.

any workarounds?

thanks in advance!

this excellent catch - , agree there bug in ctp here. dug , here's what's going on:

this combination of ctp implementation of async compiler transformations, existing behavior of tpl (task parallel library) .net 4.0+. here factors @ play:

  1. the body source translated part of real clr-finally body. desirable many reasons, 1 of can clr execute without catching/rethrowing exception time. simplifies our code gen degree - simpler code gen results in smaller binaries once compiled, desired many of our customers. :)
  2. the overarching task func(int n) method real tpl task. when await in consumer(), rest of consumer() method installed continuation off of completion of task returned func(int n).
  3. the way ctp compiler transforms async methods results in return being mapped setresult(...) call prior real return. setresult(...) boils down call taskcompletionsource<>.trysetresult.
  4. taskcompletionsource<>.trysetresult signals completion of tpl task. instantly enabling continuations occur "sometime". "sometime" may mean on thread, or in conditions tpl smart , says "um, might call on same thread".
  5. the overarching task func(int n) becomes technically "completed" right before gets run. means code awaiting on async method may run in parallel threads, or before block.

considering overarching task supposed represent asynchronous state of method, fundamentally shouldn't flagged completed until @ least user-provided code has been executed per language design. i'll bring anders, language design team, , compiler devs looked at.


scope of manifestation / severity:

you typically won't bit bad in wpf or winforms case have sort of managed message loop going on. reason why await on task implementations defer synchronizationcontext. causes async continuations queued on pre-existing message loop run on same thread. can verify changing code run consumer() in following way:

    dispatcherframe frame = new dispatcherframe(exitwhenrequested: true);     action asyncaction = async () => {         await consumer();         frame.continue = false;     };     dispatcher.currentdispatcher.begininvoke(asyncaction);     dispatcher.pushframe(frame); 

once run inside context of wpf message loop, output appears expect it:

consumer: before await #1     func: begin #1     func: end #1     func: #1 consumer: after await #1 consumer: before await #2     func: begin #2     func: end #2     func: #2 consumer: after await #2 consumer: after loop after wait 

workaround:

alas, workaround means changing code not use return statements inside try/finally block. know means lose lot of elegance in code flow. can use async helper methods or helper lambdas work around this. personally, prefer helper-lambdas because automatically closes on locals/parameters containing method, keeps relevant code closer.

helper lambda approach:

static async task<int> func( int n ) {     int result;     try     {         func<task<int>> helperlambda = async() => {             console.writeline( "    func: begin #{0}", n );             await taskex.delay( 100 );             console.writeline( "    func: end #{0}", n );                     return 0;         };         result = await helperlambda();     }         {         console.writeline( "    func: #{0}", n );     }     // since func(...)'s return statement outside try/finally,     // body execute first, in face of bug.     return result; } 

helper method approach:

static async task<int> func(int n) {     int result;     try     {         result = await helpermethod(n);     }         {         console.writeline("    func: #{0}", n);     }     // since func(...)'s return statement outside try/finally,     // body execute first, in face of bug.     return result; }  static async task<int> helpermethod(int n) {     console.writeline("    func: begin #{0}", n);     await taskex.delay(100);     console.writeline("    func: end #{0}", n);     return 0; } 

as shameless plug: we're hiring in languages space @ microsoft, , looking great talent. blog entry here full list of open positions :)


Comments

Popular posts from this blog

apache - Add omitted ? to URLs -

redirect - bbPress Forum - rewrite to wwww.mysite prohibits login -

php - How can I stop spam on my custom forum/blog? -