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

Bind of Async<> within task{} should start on the same thread #14499

Conversation

T-Gro
Copy link
Member

@T-Gro T-Gro commented Dec 20, 2022

This addresses #14490 .

I am now of the opinion of the current behavior being a bug, and this change should be treated like a bugfix.

None of the docs for task{} and async{} mention starting work on a new thread, this operation is considered to be done explicitly by user code instead.

The proposed change for 'async{} inside task{}' is also consistent with 'async in async' situation, causing less surprises when moving between task{} and async{}.

When it comes to backwards compatibility issues, GUI/main thread involvement is one thing to have in mind.
In the actual behavior's, the code could start on main thread, do the inner async{} elsewhere, and then return.
The proposed change will keep the same context, which I again see as a bugfix - both for correctness sake, but especially for resource utilization.

@T-Gro T-Gro requested a review from a team as a code owner December 20, 2022 16:07
psfinaki
psfinaki previously approved these changes Dec 20, 2022
@vzarytovskii
Copy link
Member

Please, bump FSharp.Core version, since it's a change in behaviour.
@KevinRansom wdyt - is minor version bump enought?

eng/Versions.props Outdated Show resolved Hide resolved
Copy link
Member

@vzarytovskii vzarytovskii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be completely fair, I'm still unsure if it was not a desired behaviour (starting on a thread pool).

@dsyme was it a deliberate choice?

@T-Gro T-Gro requested review from dsyme and KevinRansom December 21, 2022 11:55
@T-Gro
Copy link
Member Author

T-Gro commented Dec 21, 2022

To be completely fair, I'm still unsure if it was not a desired behaviour (starting on a thread pool).

@dsyme was it a deliberate choice?

For full completeness and why I believe this should be treated like a bug/inconsistency:

async inside Task: Outer task before: 1, inner async: 8, after inner async: 8, outside of the CE: 1
Task inside Task: Outer task before: 1, inner task: 1, after inner task: 1, outside of the CE: 1
async inside async: Outer async before: 1, inner async: 1, after inner async: 1, outside of the CE: 1
Task inside async: Outer async before: 1, inner task: 1, after inner task: 1, outside of the CE: 1

Code:

open System.Threading

let getId() = Thread.CurrentThread.ManagedThreadId

let innerAsync() = async { return getId()  }
let innerTask() = task { return getId()  }

let asyncInTask() =
    let t = task {
        let a = getId()
        let! b = innerAsync()
        let c = getId()
        return $"Outer task before: {a}, inner async: {b}, after inner async: {c}"
    }
    let d = getId()
    $"Async inside Task: {t.Result}, outside of the CE: {d}"

let taskInTask() =
    let t = task {
        let a = getId()
        let! b = innerTask()
        let c = getId()
        return $"Outer task before: {a}, inner task: {b}, after inner task: {c}"
    }
    let d = getId()
    $"Task inside Task: {t.Result}, outside of the CE: {d}"

let asyncInAsync() =
    let t = async {
        let a = getId()
        let! b = innerAsync()
        let c = getId()
        return $"Outer async before: {a}, inner async: {b}, after inner async: {c}"
    }
    let d = getId()
    $"Async inside async: {(t |> Async.StartImmediateAsTask).Result}, outside of the CE: {d}"

let taskInsideAsync() =
    let t = async {
        let a = getId()
        let! b = innerTask() |> Async.AwaitTask
        let c = getId()
        return $"Outer async before: {a}, inner task: {b}, after inner task: {c}"
    }
    let d = getId()
    $"Task inside async: {(t |> Async.StartImmediateAsTask).Result}, outside of the CE: {d}"

printfn "%s" (asyncInTask())
printfn "%s" (taskInTask())
printfn "%s" (asyncInAsync())
printfn "%s" (taskInsideAsync())

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Dec 22, 2022

@T-Gro that’s an excellent summary. I agree, this looks like a minor oversight and should be fixed, ideally in a point release (I assume we won’t publish fixes on the 6.0.x branch anymore, right? This’ll be just 7.0.x? As that’d be an even greater reason in my book to not wait until 8.x).

Do note that similar wrong behaviour exists for running an async computation and hopping to a different thread immediately (as opposed to when nested), see original issue for details.

eng/Versions.props Outdated Show resolved Hide resolved
The automatic versioning will cover for this with next product release.
Copy link
Member

@vzarytovskii vzarytovskii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, since it's an implementation detail change - not really explicitly breaking.
I'm just wondering what's what the thinking behind having current behaviour in the first place. Will discuss with Don in NY.

@T-Gro T-Gro merged commit 35971aa into main Dec 27, 2022
@abelbraaksma
Copy link
Contributor

To answer my own question, and for posterity, this fix will only live in the F# Core 7.x.x versions published after today, there will not be a new 6.0.x (afaik).

@T-Gro
Copy link
Member Author

T-Gro commented Dec 29, 2022

To answer my own question, and for posterity, this fix will only live in the F# Core 7.x.x versions published after today, there will not be a new 6.0.x (afaik).

Sorry for the radio silence -> yes, this is the plan.

I do not see this as a 'showstopper' kind of bug that should be backported, even though we technically can if this causes enough pain.

@T-Gro T-Gro deleted the 14490-binding-to-asynct-in-a-task-ce-uses-startastask-which-involves-a-context-switch branch January 2, 2023 08:47
@dsyme
Copy link
Contributor

dsyme commented Jan 3, 2023

I agree with the fix :) Nice job

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

Successfully merging this pull request may close these issues.

Binding to Async<'T> in a task CE uses StartAsTask, which involves a context switch
6 participants