Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Async.FromBeginEnd calling End function when it shouldn't?

I am doing some low level socket work using F# and am making everything asynchronous. I have a socket that I am using to listen for connections using an async workflow to handle them so it is using this to wrap the Socket.BeginListen() and Socket.EndListen().

member socket.AsyncAccept () =
    Async.FromBeginEnd( socket.BeginAccept, endOrDisposed socket.EndAccept null )

When I want to stop listening, at the moment I am doing a Socket.Close() on it which will cause the async operation started by Socket.BeginAccept() to complete.

Originally I then ran into a problem as the FromBeginEnd() function would always call the EndXXX() function no matter how the operation started by BeginXXX() completed. In some cases this would lead to an ObjectDisposed exception by the EndXXX() function as by the time it was called the Socket had been closed and disposed. I added a little handler function which would filter those exceptions out thus:

let endOrDisposed endFunc defaultResult iar = try endFunc iar with | _ -> defaultResult

This does the trick, but not when running in debug. I am well aware that the Just My Code option can be used to hide the exception but this could be happening with some other IO operations on a more frequent basis so I also would rather not have processor time wasted on raising and catching exceptions which shouldn't really be there in the first place. Also I might not want to hide where other exceptions are being thrown as they could by in areas where I do need to debug.

I've looked at the code for Async.FromBeginEnd and it is wired to always call the EndXXX() function no matter what, I'm not sure this is the best behaviour, perhaps I should write a replacement for it? Or does anyone have any other ideas for elegant solutions?


I just found (sure I'd looked before though) this in the docs:

To cancel a pending call to the BeginAccept() method, close the Socket. When the Close() method is called while an asynchronous operation is in progress, the callback provided to the BeginAccept() method is called. A subsequent call to the EndAccept() method will throw an ObjectDisposedException to indicate that the operation has been cancelled.

I still don't like the exception, even if it is by design. I'd prefer to find a way to not call EndXXX() on a disposed object. Perhaps I can mix some CancellationToken magic into this somehow?

like image 896
David Avatar asked May 03 '26 00:05

David


1 Answers

I would make use of the optional cancelAction argument to Async.FromBeginEnd here:

type Socket with
  member this.AsyncAccept () =
    let canceled = ref false
    let endAccept iar = if not !canceled then this.EndAccept iar else null
    let cancel () = canceled := true; this.Close ()
    Async.FromBeginEnd (this.BeginAccept, endAccept, cancelAction = cancel)

This way you can use Async's builtin cancellation functionality (which is of course based on CancellationToken) without touching a CancellationToken directly. (I.e., you should not be calling Socket.Close for cancellation purposes.)

like image 182
ildjarn Avatar answered May 04 '26 19:05

ildjarn