Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Easy way to get result synchronously? #28

Closed
isaacabraham opened this issue Jun 17, 2019 · 5 comments
Closed

Easy way to get result synchronously? #28

isaacabraham opened this issue Jun 17, 2019 · 5 comments

Comments

@isaacabraham
Copy link
Collaborator

isaacabraham commented Jun 17, 2019

Is there an equivalent of Async.RunSynchronously aside from the .Result? I'm thinking as a function that the task can be piped to.

@rspeele
Copy link
Owner

rspeele commented Jun 18, 2019

Nope. Even if the builder handled this case specially, you could await any sort of Task in your task {...} block and end up subject to the same problems as you'll find with .Result. Therefore this cannot be supported any better than just using .Result.

@rspeele rspeele closed this as completed Jun 18, 2019
@isaacabraham
Copy link
Collaborator Author

@rspeele this is to stop getting spurious compiler errors like this:

[<EntryPoint>]
let main argv =
    task {
        return 0
    }.Result

which gives a compiler error: "This value is not a function and cannot be applied". The "fix" in this case is to either separate out the creation of the task and the Result member access, or wrap the task { } block in parentheses, neither of which are idiomatic.

I'd suggest adding something like Task.RunSynchronously to both (a) provide a way to avoid the error above, and (b) to provide some equivalence for people coming from the async { } world to Async.RunSynchronously.

@isaacabraham
Copy link
Collaborator Author

You could create an type extension on top of Task quite easily to achieve the above:

[<Extension>]
type Task() =
    static member RunSynchronously(task:_ Task) = task.Result

[<EntryPoint>]
let main argv =
    task {
        return 0
    } |> Task.RunSynchronously

@rspeele
Copy link
Owner

rspeele commented Jun 19, 2019

Sorry, I'm not in favor of it. Four reasons:

  1. Blocking the thread on a Task via .Result or .Wait() is not only wasteful, but depending on the synchronization context, can cause deadlocks. There should usually not be a reason to do this other than at the very top level of an app, e.g. just once within main(argv). I think including a function to make this more convenient would make it look like it is a recommended practice to use it frequently.

  2. Naming it RunSynchronously would match Async.RunSynchronously, but shouldn't, because the two are very different in this regard. Async is more like a Func<Task> and can be run many times. The task{} block is already running and invoking .Result will only block for its result, not run it again.

  3. Having an alternate way to do the same thing as .Result is confusing. Supposing I've never seen the library docs but am reading a block of code that uses this library, I would not know at a glance that this was just another way to write .Result. I might mistake it for something like Task.Run. If this was something that had to be written very frequently, the convenience of being able to use F#-idiomatic syntax might outweigh this disadvantage, but as mentioned in (1) that is not the case here.

  4. In the long term, TaskBuilder.fs will hopefully be obsoleted by inclusion in FSharp.Core. The smaller we keep our API, the less work it will be for downstream users to port their code to the compiler-optimized built in Task CE of the future.

@toburger
Copy link

A place where you need the synchronous result often is in F# scripting code or at the REPL.
But I think a simple code snippet to facilitate the usage at the beginning of the script is enough for this scenario.
That said I share your opinion of not providing such a helper out of the box.

Maybe a "universal" helper that awaits every kind of asynchronous operation for those scenario would be nice (thinking of the @ operator used in clojure, which awaits every peculiarity of asynchronous computations).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants