mashrur
mashrur

Reputation: 465

F# Async Task Cancellation without Token

I am trying to parse hundreds of C source files to map dozens of software signal variables to the names of physical hardware pins. I am trying to do this asynchronously in F#

IndirectMappedHWIO
|> Seq.map IndirectMapFromFile  //this is the function with the regex in it
|> Async.Parallel          
|> Async.RunSynchronously  

The issue is that I cannot figure out how to pass in a CancellationToken to end my task. Each task is reading around 300 C files so I want to be able to stop the task's execution as soon as the regex matches. I tried using Thread.CurrentThread.Abort() but that does not seem to work. Any ideas on how to pass in a CancellationToken for each task? Or any other way to cancel a task based on a condition?

let IndirectMapFromFile pin = 
async {    
    let innerFunc filename = 
        use streamReader = new StreamReader (filePath + filename)
        while not streamReader.EndOfStream do
            try
                let line1 = streamReader.ReadLine()
                streamReader.ReadLine() |> ignore
                let line2 = streamReader.ReadLine()

                if(obj.ReferenceEquals(line2, null)) then
                    Thread.CurrentThread.Abort()  //DOES NOT WORK!!
                else    
                    let m1 = Regex.Match(line1, @"^.*((Get|Put)\w+).*$");
                    let m2 = Regex.Match(line2, @"\s*return\s*\((\s*" + pin.Name + "\s*)\);");
                    if (m1.Success && m2.Success) then
                        pin.VariableName <- m1.Groups.[1].Value
                        Thread.CurrentThread.Abort() //DOES NOT WORK!!
                    else
                        ()
            with
            | ex -> ()
        ()

    Directory.GetFiles(filePath, "Rte*") //all C source and header files that start with Rte
    |> Array.iter innerFunc
}

Upvotes: 2

Views: 439

Answers (1)

Vandroiy
Vandroiy

Reputation: 6223

Asyncs cancel on designated operations, such as on return!, let!, or do!; they don't just kill the thread in any unknown state, which is not generally safe. If you want your asyncs to stop, they could for example:

  • be recursive and iterate via return!. The caller would provide a CancellationToken to Async.RunSynchronously and catch the resulting OperationCanceledException when the job is done.

  • check some thread-safe state and decide to stop depending on it.

Note that those are effectively the same thing: the workers who iterate over the data check what is going on and cancel in an orderly fashion. In other words, it is clear when exactly they check for cancellation.

Using async cancellation might result in something like this:

let canceler = new System.Threading.CancellationTokenSource()
let rec worker myParameters =
    async {
        // do stuff
        if amIDone() then canceler.Cancel()
        else return! worker (...) }

let workers = (...) |> Async.Parallel

try Async.RunSynchronously(workers, -1, canceler.Token) |> ignore
with :? System.OperationCanceledException -> ()

Stopping from common state could look like this:

let keepGoing = ref true
let rec worker myParameters =
    if !keepGoing then
        // do stuff
        if amIDone() then keepGoing := false
        worker (...)

let makeWorker initParams = async { worker initParams }
// make workers
workers |> Async.Parallel |> Async.RunSynchronously |> ignore

If the exact timing of cancellation is relevant, I believe the second method may not be safe, as there may be a delay until other threads see the variable change. This doesn't seem to matter here though.

Upvotes: 2

Related Questions