System.NullReferenceException in new `task` CE in F# 6 · Issue #12359 · dotnet/f...
source link: https://github.com/dotnet/fsharp/issues/12359
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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 thev
becomes 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
while
ortry
orfor
or a construct such asArray.map
that 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 |]).Result
Alternatively 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 |]).Result
Other workarounds are shown above
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK