I suggest you ...

Enable full-scale CPS via async/await by allowing user-defined classes instead of Task<T> etc

In the async CTP, I have to use the return type Task<T> (or Task) for any async method. Behind the scenes, this code is expanded do invoke an AsyncMethodBuilder and all kinds of things. The expansion basically allows for generic continuation-passing style. However, the concrete classes Task<T> etc. are really specialized for asynchronous programming using multi-threading.

If I could use my own type instead of Task<T>/Task, I could use async/await for anything. I could create methods that present a web page in the middle of their execution, and just continue at the next postback. (In fact, that's what I want to do.) But I could also implement all kinds of control flow manipulation.

public class MyTask<T>
static MyMethodBuilder<T> GetMethodBuilder (...) {...}

async MyTask<int> foo ()

- CPS is extremely powerful and can be really useful
- you would impress the lambda-the-ultimate crowd
- C# has a history of transforming code to a different text representation and then try and compile it (started with using/Dispose() and continued until LINQ)

88 votes
Sign in
Sign in with: facebook google
Signed in as (Sign out)
You have left! (?) (thinking…)
stefan.wenig shared this idea  ·   ·  Flag idea as inappropriate…  ·  Admin →

Thanks for taking the time to share this suggestion. This item has been around for a couple of versions of Visual Studio and we haven’t acted on it. Looking at the VS “15” plans, we’re not going to take action on this item, so we’re going to close it. If the suggestion is still relevant, please either take a look to see if there’s another suggestion that’s similar that you can vote on, or open a new suggestion.

- The Visual Studio Team


Sign in
Sign in with: facebook google
Signed in as (Sign out)
  • Mads Torgersen [MSFT] commented  ·   ·  Flag as inappropriate

    For background we did try to design async methods with arbitrary return types (as long as they supported a certain pattern), but there were several subtleties that caused us to give that up. One was that overload resolution needs to "see through" to the underlying result type of a "Task" and we couldn't think of a good way to generalize that.

    To be clear, you can build pretty much any functionality on top of Task<T>. It is not strongly tied to threads, and it is quite efficient. So functionally you can build other types with async methods by wrapping their Task<T> results with your own type.

    I believe this holds even for serialization. If you wrap the Task<T> returned from an async method in your own serializable type (say MyTask<T>) and never expose the Task<T> itself to the outside world, then your type can track the exact state of the Task<T> by registering a single continuation to that Task<T>. MyTask<T> can track continuations whichever way it wants, and run them whichever way it wants when it gets triggered from the completion of the underlying Task<T>.

    Make sense?


  • stefan.wenig commented  ·   ·  Flag as inappropriate

    Joshua, I have no issues with the GetAwaiter pattern or anything else the compiler does. It's only the hard-coded usage of the Task class that carries all the often unneccessary threading baggage. But to be honest, my only real issue is the serialization problem. Everything else I tried can be solved with Task/Task<T>, even if it does carry a lot of weight.

  • Joshua A. Schaeffer commented  ·   ·  Flag as inappropriate

    You really should learn the GetAwaiter() pattern and INotifyCompletion/ICriticalNotifyCompletion, then you would realize that asynchrony has nothing to do with threads and everything to do with linearizing event-driven coding. Threading is just the obvious best first application of it.

  • stefan.wenig commented  ·   ·  Flag as inappropriate

    Just to add another argument:
    Think how much cooler a node.cs with automatic CPS would be than node.js with all that manual inside-out CPS-style coding! And considering all the hype that node.js gets these days...
    Unfortunately, Task<T> is all about threading, and node.js is all about how asynchronous programming is more efficient without threads...

  • Qwertie commented  ·   ·  Flag as inappropriate

    I have no votes available for this, but I totally agree. I think async/await should efficiently support arbitrary "task" types, just as foreach() does early binding and does not depend on IEnumerable.

    In particular, I would like to find a way to use async/await to support *efficient* nested iterators (like Cω's stream flattening), because in principle it should be possible, as I said earlier on Eric Lippert's blog: http://blogs.msdn.com/b/ericlippert/archive/2011/10/03/async-articles.aspx

    If we are forced to use Task<T> (which I'm honestly not familiar with), we are subject to whatever limitations and performance constraints those classes were designed with.

  • james.manning commented  ·   ·  Flag as inappropriate

    Jon corrected me - async methods can only return void, Task, and Task<T>. Sorry for my confusion!

  • james.manning commented  ·   ·  Flag as inappropriate

    I don't fully understand the async stuff as of yet, but my vague understanding thus far is that you could use your own types as awaitable (the 'MyTask' class in your code) just by having a GetAwaiter method available (as you mentioned, it's the same kind of 'just find and use this method' as foreach/GetEnumerator and others).

    If I'm wrong and what you're asking for isn't doable with the existing compiler approach, is there any chance you could phrase it in terms of this Jon Skeet blog post to help others (or, at least, me :) understand how you're asking for something different than allowing your own class to be awaitable?


    Thanks, Stefan!

  • stefan.wenig commented  ·   ·  Flag as inappropriate

    PS: this resulted from the discussion at https://connect.microsoft.com/VisualStudio/feedback/details/620941/tasks-returned-by-async-methods-should-be-serializable-async-ctp

    Task, AsyncMethodBuilder and friends cannot be serialized, simpler classes without all the built-in asynch execution features could more easily. I'm sure there are other cases too where the specific implementation of Task & Co. would get in the way, but haven't tested.

Feedback and Knowledge Base