5

System.NullReferenceException in new `task` CE in F# 6 · Issue #12359 · dotnet/f...

 2 years ago
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.
neoserver,ios ssh client

Copy link

sasmithjr commented 14 days ago

edited

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

  1. You are in a task { ... }, and
  2. You are in a binding let v = ... where the v becomes a state variable of the task state machine, because it is used after an asynchronous point
  3. The right-hand-side of a binding uses either a while or try or for or a construct such as Array.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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK