Open dsyme opened 6 years ago
Note that there is a question for async/asyncSeq about whether such operations would be performed if the operation is cancelled, and how that would be implemented. try/finally
are executed during cancellation. The above was on the assumption that an exception or normal result had occurred....
I can see two possible ways to handle cancellation in finally!
blocks for asyncs:
finally!
block is always run when the operation is cancelled, and it's up to the programmer to ensure that whatever he does in the finally!
block will not cause problems if the operation is being cancelled. There is no way for him to query whether the finally!
is running during a normal result, an exception, or a cancellation, therefore caveat scriptor.finally!
block is always run when the operation is cancelled, but the AsyncBuilder (and related builders like AsyncSeq) can provide some way to tell, inside the finally!
block, whether a cancellation is happening. So the programmer can write something like:asyncSeq {
try
...
finally!
let token = getCancellationTokenForThisBlock()
if token.IsCancellationRequested then
doVitalCleanup()
else
yield! longRunningAsyncProcess()
yield UpdateItemsComplete
doVitalCleanup()
}
Note that I did not list "finally!
blocks are not run during cancellation" as an option, because I believe those semantics would be wrong. If finally
is always run during cancellation, then finally!
should behave similarly.
And BTW, I like the issue title. :smile:
Why does it need finally!
? Can't it just be another overload for the builder, so that if your finally block returns an instance of the monad it's used?
Why not catch!
for Async.catch and other context based error handling,
(like custom logging and the railway-oriented style)
Edit: with
is overloaded keyword, also used in match ... with, so we can't use that with bang?
Basically finally!
would be quite near to match!
: https://github.com/Microsoft/visualfsharp/pull/4427
There is no currently easy way to say "Await async whatever, and then log this message". So to make a common logging for failures, companies have to do something like:
inline
all the common logging. Code stays simple but compiler starts to be slowasyncOmega
-style own builder to replace current async.Clearly it's a problem. I think catch!
and finally!
would solve this, but internally the same problem has to be dealt with, right?
Can't compiler just look into catch
and finally
blocks and do right job without any suffix?
The more general problem is already that if you do a lot of code inside a computation expression (like async), you lose the interactive-driven-development as you cannot easily send a part of computation expression code to FSI.
This seems to be required to implement native support for IAsyncDisposable
IAsyncDisposable sounds kind of funny: I thought the original idea of IDisposable was to not left resources hanging...then what is the purpose of async disposable?
IAsyncDisposable sounds kind of funny: I thought the original idea of IDisposable was to not left resources hanging...then what is the purpose of async disposable?
Same idea, but sometimes you need to call async methods during the cleanup
I think and want this feature. I wrote simulated asynchronous finally block in hand-made, I feel it's too hard to implements.
First version (simplicity but will be hard blocked):
Final result:
They couldn't write it average level engineer...
@dsyme I've been hacking FSharp.Core a little bit for (well not exactly finally! feature, but allowing use to consume a synthetic IFSharpAsyncDisposable type which has a unit -> Async'unit Dispose method). The problem I've found is cancellation. Usually simple unit -> unit bindings are always called because they are not Delayed (Async.Delay) and thus not checked for cancellation. On the other hand those async Dispose had that check and in fact did not properly Dispose.
The possible hack was to either change CancellationToken in AsyncActivation.aux.Token for cconts or to add a boolean value to AsyncActivation indicating that we are now in "forced" ccont state.
So the problem is: how to deal with cancellation in this case? On the other hand computation is cancelled and it's somewhat logical to cancel all async computations, finally included. On the other hand it makes sense to force finally computations because user could take some kind of distributed lock in async operation and it needs to be freed and so on.
ping @dsyme I've skimmed through the issue again and you've raised this exact topic in 2018. Funny how I just missed it. Can you please share your thoughts on how should we deal with cancellation in general? And I guess hacks mentioned above are not the really desired ones am I right?
I've marked this approved-in-principle - I don't know why it wasn't approved
Can you please share your thoughts on how should we deal with cancellation in general?
@En3Tho My apologies for not replying to the above.
It would be great to get a PR together and discuss this in context
I propose we add
try .. finally! ...
to allowfinally
actions to be monadic.The current computation-expression desugaring of
try .. finally ...
allows only aunit -> unit
action in the compensation branch.. However there are cases where an asynchronous or other monadic imperative action makes sense in exception compensation, e.g. using AsyncSeq:The current way of doing this in F# is some mess like this:
The same effect can also currently be achieved with a custom operation, something like this, but just supporting
finally!
looks more sensible:Pros and Cons
The advantages of making this adjustment to F# are orthogonality/simplicity.
The disadvantages of making this adjustment to F# are cost.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related Items