{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/7169","repository_url":"https://api.github.com/repos/dotnet/roslyn","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/7169/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/7169/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/7169/events","html_url":"https://github.com/dotnet/roslyn/issues/7169","id":119847296,"number":7169,"title":"Proposal: arbitrary task-like types returned from async methods","user":{"login":"ljw1004","id":3316258,"avatar_url":"https://avatars.githubusercontent.com/u/3316258?v=3","gravatar_id":"","url":"https://api.github.com/users/ljw1004","html_url":"https://github.com/ljw1004","followers_url":"https://api.github.com/users/ljw1004/followers","following_url":"https://api.github.com/users/ljw1004/following{/other_user}","gists_url":"https://api.github.com/users/ljw1004/gists{/gist_id}","starred_url":"https://api.github.com/users/ljw1004/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ljw1004/subscriptions","organizations_url":"https://api.github.com/users/ljw1004/orgs","repos_url":"https://api.github.com/users/ljw1004/repos","events_url":"https://api.github.com/users/ljw1004/events{/privacy}","received_events_url":"https://api.github.com/users/ljw1004/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Area-Language%20Design","name":"Area-Language Design","color":"5319e7"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Discussion","name":"Discussion","color":"fbca04"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Feature%20Request","name":"Feature Request","color":"fc2929"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":25,"created_at":"2015-12-02T01:32:05Z","updated_at":"2016-04-10T16:09:39Z","closed_at":null,"body":"**Download**\r\n* Note: this prototype doesn't yet work correctly with lambdas. I'll work on that.\r\n* [ArbitraryAsyncReturns.zip](https://github.com/ljw1004/roslyn/raw/features/async-return/ArbitraryAsyncReturns.zip) [22mb]\r\n* INSTALL: Unzip the file. Quit VS. Double-click to install in this order: (1) Roslyn.VisualStudio.Setup.vsix, (2) Roslyn.Compilers.Extension.vsix, (3) ExpressionEvaluatorPackage.vsix. I don't think the others are needed.\r\n* TRY IT OUT: samples can be found [here](https://github.com/ljw1004/roslyn/tree/features/async-return/ArbitraryAsyncReturns-sample)\r\n* UNINSTALL: Sorry, uninstall is unpleasant. You have to delete the entire folder `%userprofile%\\AppData\\Roaming\\Microsoft\\VisualStudio` and `%userprofile%\\AppData\\Local\\Microsoft\\VisualStudio`. And doing so will reset all your VS settings to default. (It should be possible to uninstall much more easily, just via Tools>Extensions, but that didnt' work for me).\r\n\r\n-------\r\n\r\n# Introduction\r\n\r\nIt’s an ongoing user request for async methods to return arbitrary types. We’ve heard this particularly from people who want to return value Task-like types from their async methods for efficiency. In UWP development, it would be nice also to return IAsyncAction directly rather than having to return a Task and then write a wrapper.\r\n\r\nThe initial C# prototype of async did allow to return arbitrary types from async methods, but that was cut because we didn’t find a neat way to integrate it with generic type inference. Here’s a recap of the problem:\r\n\r\n```cs\r\nvar xf = f(async () => {return 3;});\r\nvar xg = g(async () => {return 3;});\r\nvar xh = h(async () => {return 3;});\r\nvar xk = h(async () => {return 3;});\r\n\r\nT f(Func<Task<T>> lambda) => lambda().Result;\r\nT g(Func<MyTasklike<T>> lambda) => lambda().MyResultlike;\r\nT h(Func<MyPerverse<T>> lambda) => lambda().MyPerverseResult;\r\nT k(Func<MyWeird<T>> lambda) => lambda.MyWeirdResult;\r\n\r\n// f: the builder for Task<T> lets you do “return 3;” and it infers that T should be int\r\n// g: the builder for MyTasklike<T> lets you do “return 3;” and it infers that T should be int\r\n// h: the builder for MyPerverse<T> lets you do “return 3;” but it infers that T should IEnumerable<int>\r\n// k: the builder for MyWeird<T> lets you do “return 3;” but this has no bearing on what T should be: type inference failure\r\n```\r\n\r\nThe feature was cut because we had no solution for the full generality of situations like `MyPerverse<T>` or `MyWeird<T>`: no way to make the leap from the type of the operand of the return statement, to the generic type arguments of the Task-like type.\r\n\r\n\r\n__Proposal__: let’s just abandon the full generality cases `h` and `k`. They’re perverse and weird. Let’s say that async methods can return any type `Foo` (with similar semantics to `Task`) or can return any type `Foo<T>` (with similar semantics to `Task<T>`).\r\n\r\nMore concretely, let's say that the return type of an async method or lambda can be any _task-like_ type. A task-like type is one that has either have zero generic type parameters (like `Task`) or one generic type parameter (like `Task<T>`), and which is declared in one of these two forms, with an attribute:\r\n\r\n```cs\r\n[TaskLike(typeof(FooBuilder))] struct Foo { … }\r\nstruct FooBuilder { … similar to AsyncVoidMethodBuilder … }\r\n\r\n[TaskLike(typeof(FooBuilder<T>))] struct Foo<T> { … }\r\nstruct FooBuilder<T> { … similar to AsyncTaskMethodBuilder<T> … }\r\n```\r\n\r\nAll the type inference and overload resolution that the compiler does today for `Task<T>`, it should also do the same for other tasklikes. In particular it should work for target-typing of async lambdas, and it should work for better betterness.\r\n\r\nFor backwards-compatibility, if the return type is `System.Threading.Tasks.Task` or `Task<T>` and also lacks the `[TaskLike]` attribute, then the compiler implicitly picks `System.Runtime.CompilerServices.AsyncVoidMethodBuilder` or `AsyncTaskMethodBuilder` for the builder.\r\n\r\n\r\n## Semantics\r\n[edited to incorporate @ngafter's comments below...]\r\n\r\nThe VB spec currently explains the semantics of async methods+lambdas like this:\r\n>    If the async method is a Function with return type `Task` or `Task(Of T)` for some `T`, then an object of that type implicitly created, associated with the current invocation. This is called an async object and represents the future work that will be done by executing the instance of the async method. When control resumes in the caller of this async method instance, the caller will receive this async object as the result of its invocation.\r\n\r\nThis would be changed to say that, by assumption, the async method's return type is a Tasklike, which has an associated builder type (specified by attribute). An object of that builder type is implicitly created by invoking the builder's static Create method, and the return value of the async method is the result of the `.Task` property on that builder instance. (or void).\r\n\r\nThe spec goes on to explain the semantics of exiting the method:\r\n>    If control flow exits through an unhandled exception, then the async object’s status is set to `TaskStatus.Faulted` and its `Exception.InnerException` property is set to the exception (except: certain implementation-defined exceptions such as `OperationCanceledException` change it to `TaskStatus.Canceled`). Control flow resumes in the current caller. Otherwise, the async object’s status is set to `TaskStatus.Completed`. Control flow resumes in the current caller.\r\n\r\nThis would be amended to say that if control flow exits through an unhandled exception `ex` then `.SetException(ex)` is called on the builder instance associated with this method instance. Otherwise, the `.SetResult()` is called, with or without an argument depending on the type.\r\n\r\nThe spec currently gives semantics for how an async method is started:\r\n>    The instance's control point is then set at the first statement of the async method body, and immediately starts to execute the method body from there.\r\n\r\nThis would be amended to say that the builder's `Start` method is invoked, passing an instance that satisfies the `IAsyncStateMachine` interface. The async method will start to execute when the builder first calls `MoveNext` on that instance, (which it may do inside `Start` for a hot task, or later for a cold task). If the builder calls `MoveNext` more than once (other than in response to `AwaitOnCompleted`) the behavior is undefined. If ever the builder's `SetStateMachine` method is invoked, then any future calls it makes to `MoveNext` must be through this new value.\r\n\r\n*Note: this allows for a builder to produce cold tasklikes.*\r\n\r\nThe spec currently gives semantics for how the await operator is executed:\r\n>    Either `ICriticalNotifyCompletion.UnsafeOnCompleted` is invoked on the awaiter (if the awaiter's type `E` implements `ICriticalNotifyCompletion`) or `INotifyCompletion.OnCompleted` (otherwise). In both cases it passes a resumption delegate associated with the current instance of the async method. The control point of the current async method instance is suspended, and control flow resumes in the current caller. If later the resumption delegate is invoked, the resumption delegate first restores `System.Threading.Thread.CurrentThread.ExecutionContext` to what it was at the time `OnCompleted` was called, then it resumes control flow at the control point of the async method instance.\r\n\r\nThis would be amended to say that either `builder.AwaitOnCompleted` or `builder.AwaitUnsafeOnCompleted` is invoked. The builder is expected to synthesizes a delegate such that, when the delegate is invoked, then it winds up calling `MoveNext` on the state machine. Often the delegate restores context.\r\n\r\n*Note: this allows for a builder to be notified in when a cold await operator starts, and when it finishes.*\r\n\r\n\r\n*Note: the Task type is not sealed. I might chose to write `MyTask<int> FooAsync()` which returns a subtype of Task, but also performs additional logging or other work.*\r\n\r\n\r\n## Limitations\r\n\r\nThis scheme wouldn't be able to represent the WinRT types `IAsyncOperationWithProgress` or `IAsyncActionWithProgress`. It also wouldn't be able to represent the fact that WinRT async interfaces have a cancel method upon them. We might consider allowing the async method to access its own builder instance via some special keyword, e.g. `_thisasync.cancel.ThrowIfCancellationRequested()`, but that seems too hacky and I think it's not worth it.\r\n\r\n## Compilation notes and edge cases\r\n\r\n**Concrete tasklike**. The following kind of thing is conceptually impossible, because the compiler needs to know the *concrete* type of the tasklike that's being constructed (in order to construct it).\r\n```cs\r\nclass C<T> where T : MyTasklike {\r\n    async T f() { } // error: the return type must be a concrete type\r\n}\r\n```\r\n\r\n**Incomplete builder: binding**. The compiler should recognize the following as an async method that doesn't need a return statement, and should bind it accordingly. There is nothing wrong with the `async` modifier nor the absence of a `return` keyword. The fact that `MyTasklike`'s builder doesn't fulfill the pattern is an error that comes later on: it doesn't prevent the compiler from binding method `f`.\r\n```cs\r\nclass C { async MyTasklike f() { } }\r\n[Tasklike(typeof(string))] class MyTasklike {}\r\n```\r\n\r\n**Wrong generic**. To be a tasklike, a type must (1) have a [Tasklike] attribute on it, (2) have arity 0 or 1. If it has the attribute but the wrong arity then it's not a Tasklike.\r\n```cs\r\nclass C { async MyTasklike f() { } } // okay: return type has arity 0\r\nclass C { async MyTasklike<int> f() { return 1;} } // okay: return type has arity 1:int\r\nclass C { async MyTasklike<int> f() { return \"s\";} } // error: should return an int, not a string\r\nclass C { async MyTasklike<int,int> f() { } } // error\r\n```\r\n\r\n**Incomplete builder: codegen**. If the builder doesn't fulfill the pattern, well, that's an edge case. It should definitely give errors (like it does today e.g. if you have an async Task-returning method and target .NET4.0), but it doesn't matter to have high-quality errors (again it doesn't have high quality errors today).\r\n"},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/208007434","html_url":"https://github.com/dotnet/roslyn/issues/7169#issuecomment-208007434","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/7169","id":208007434,"user":{"login":"i3arnon","id":6595156,"avatar_url":"https://avatars.githubusercontent.com/u/6595156?v=3","gravatar_id":"","url":"https://api.github.com/users/i3arnon","html_url":"https://github.com/i3arnon","followers_url":"https://api.github.com/users/i3arnon/followers","following_url":"https://api.github.com/users/i3arnon/following{/other_user}","gists_url":"https://api.github.com/users/i3arnon/gists{/gist_id}","starred_url":"https://api.github.com/users/i3arnon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/i3arnon/subscriptions","organizations_url":"https://api.github.com/users/i3arnon/orgs","repos_url":"https://api.github.com/users/i3arnon/repos","events_url":"https://api.github.com/users/i3arnon/events{/privacy}","received_events_url":"https://api.github.com/users/i3arnon/received_events","type":"User","site_admin":false},"created_at":"2016-04-10T16:09:39Z","updated_at":"2016-04-10T16:09:39Z","body":"@benaadams Oh.. right, I missed that. However it doesn't really matter as exceptions aren't necessarily raised explicitly, for example:\r\n\r\n```\r\nTask FooAsync(object value)\r\n{\r\n    var message = value.ToString();\r\n    return SendAsync(message);\r\n}\r\n\r\n```"}}