-
Notifications
You must be signed in to change notification settings - Fork 847
Description
In some cases where a let precedes a let! in the new task CE, an NRE is thrown. I'm not sure of the specifics for why it happens sometimes and not others, but I've got an example than can quickly be run through FSI.
dotnet --version: 6.0.100
OS: macOS 11.6.1
Repro steps
After using dotnet fsi, copy/paste the following code block into FSI to see the issue:
let z0 () =
task {
let w = [| 1 |] |> Array.map (fun w -> w + 1) |> Array.head
let! x = task { return 3 }
let finalResult = w + x + 3
return finalResult
}
// Throws an NRE
z0 () |> Async.AwaitTask |> Async.RunSynchronously;;If you swap the let w... and let! x... lines, it works:
let z1 () =
task {
let! x = task { return 3 }
let w = [| 1 |] |> Array.map (fun w -> w + 1) |> Array.head
let finalResult = w + x + 3
return finalResult
}
// Works
z1 () |> Async.AwaitTask |> Async.RunSynchronously;;Or a simple replacement of Array.map and Array.head works:
let increment = Array.map (fun w -> w + 1)
let head = Array.head
let z2 () =
task {
let w = [| 1 |] |> increment |> head
let! x = task { return 3 }
let finalResult = w + x + 3
return finalResult
}
// Works
z2 () |> Async.AwaitTask |> Async.RunSynchronously;;Adding a second let also works:
let z3 () =
task {
let w0 = [| 1 |] |> Array.map (fun w -> w + 1)
let w1 = w0 |> Array.head
let! x = task { return 3 }
let finalResult = w1 + x + 3
return finalResult
}
//Works
z3 () |> Async.AwaitTask |> Async.RunSynchronously;;Actual behavior
System.NullReferenceException: Object reference not set to an instance of an object. at [email protected]()
Expected behavior
I'd expect the z0() call to eventually evaluate to 8 instead of throwing an exception.
Known workarounds
The problem occurs when
- You are in a
task { ... }, and - You are in a binding
let v = ...where thevbecomes a state variable of the task state machine, because it is used after an asynchronous point - The right-hand-side of a binding uses either a
whileortryorforor a construct such asArray.mapthat is inlined to produce one of these
In short, the most common cause appears to be the use of the inlined Array.map inside task { ... }. For this case, you can either rewrite your code to avoid the use of Array.map, e.g. using Array.mapi, or define and use a non-inlined Array.map as follows:
module Array =
let map f x = FSharp.Collections.Array.map f x
let f arr =
task {
let w = arr |> Array.map (fun w -> w + 1) |> Array.head // This uses the new non-inlined Array.map
do! System.Threading.Tasks.Task.Yield()
return w + 3
}
(f [| 1 |]).ResultAlternatively using Array.mapi:
let f arr =
task {
let w = arr |> Array.mapi (fun _ w -> w + 1) |> Array.head
do! System.Threading.Tasks.Task.Yield()
return w + 3
}
(f [| 1 |]).ResultOther workarounds are shown above