我正在寻找像F#Async.Start
这样的解决方案,但没有吞下异常。我希望控制台应用程序正常死于未处理的异常。这是一个.NET Core 2.1应用程序,我需要通过响应Linux SIGTERM和SIGINT信号来处置资源。如何修改此代码,使其传播kaboom!
异常?
let mainAsync() =
async {
// blow up on purpose after a number of seconds
let seconds = 10
printfn "%d seconds and counting" seconds
do! (System.TimeSpan.FromSeconds(float seconds)).TotalMilliseconds |> int |> Async.Sleep
//failwithf "kaboom!"
// Update: failwithf does reproduce the problem
// My real app is making WCF calls that are Task based and awaiting on them
// I don't know how to make a small test case for this
// The client is throwing a TimeoutException if unable to connect
wcfClient.RetrieveServiceContentAsync someValue |> Async.AwaitTask
}
[<EntryPoint>]
let main argv =
use cancelMainAsync = new System.Threading.CancellationTokenSource()
use cancelMain = new System.Threading.AutoResetEvent(false)
let cancel() =
if not cancelMainAsync.IsCancellationRequested then
cancelMainAsync.Cancel()
cancelMain.Set() |> ignore
System.Runtime.Loader.AssemblyLoadContext.Default.add_Unloading(fun _ -> cancel())
System.Console.CancelKeyPress.Add (fun keyPress -> keyPress.Cancel <- true; cancel())
Async.Start(mainAsync(), cancelMainAsync.Token)
cancelMain.WaitOne() |> ignore
0
答案 0 :(得分:2)
正如Fyodor在评论中所述,我认为仅使用Async.RunSynchronously
就可以解决问题。当我按下CTRL + C时,我放在一起的这段代码看起来好像打印了“ Cancelled”,并且抛出了“ Kaboom!”。不这样做的例外情况:
open System
open System.Threading
let f () =
async {
printfn "Running..."
do! Async.Sleep 10000
failwith "Kaboom!"
}
[<EntryPoint>]
let main argv =
use cancellation = new CancellationTokenSource()
Console.CancelKeyPress |> Event.add (fun _ -> cancellation.Cancel(); printfn "Cancelled")
Async.RunSynchronously(f(), cancellationToken = cancellation.Token)
0
编辑
该屏幕截图显示了CTRL + C终止。请注意此版本printfn
之后的Async.RunSynchronously
,以及它如何执行。
答案 1 :(得分:2)
我需要到明天,所以这是我想出的解决方案。在这种情况下,与C#异步进行的F#异步互操作可能会非常令人沮丧。请查阅。如果有更好的解决方案,我将感到高兴。
我最终将自己的异常处理程序传递给Async.StartWithContinuations
,但是由于它是在同一线程上启动的,所以我在do! Async.SwitchToThreadPool()
上添加了mainAsync
。这样可以使CancelKeyPress
正常工作。如果不需要CancelKeyPress
,则无需将其放在另一个线程上。
主要目标是确保正确处置一些宝贵的资源。发生异常,使用ctrl + c取消控制台应用程序或杀死该控制台应用程序时,将打印“安全港”。在Visual Studio中,您可以通过单击运行时弹出的控制台窗口上的关闭按钮来终止该应用程序。退出代码的异常和取消设置有所不同。
let mainAsync() =
async {
do! Async.SwitchToThreadPool()
use precious = { new System.IDisposable with override this.Dispose() = printfn "safe haven" }
// blow up on purpose after a number of seconds
let seconds = 10
printfn "%d seconds and counting" seconds
do! (System.TimeSpan.FromSeconds(float seconds)).TotalMilliseconds |> int |> Async.Sleep
failwithf "kaboom!"
// the real app is has a System.TimeoutException being thrown from a C# Task
//wcfClient.RetrieveServiceContentAsync someValue |> Async.AwaitTask
}
[<EntryPoint>]
let main argv =
let mutable exitCode = 0
use cancelMainAsync = new System.Threading.CancellationTokenSource()
use cancelMain = new System.Threading.ManualResetEventSlim()
let cancel() =
if not cancelMainAsync.IsCancellationRequested then
cancelMainAsync.Cancel()
cancelMain.Set()
let exceptionHandler (ex: System.Exception) =
let ex =
match ex with
| :? System.AggregateException as ae ->
if ae.InnerExceptions.Count = 1 then ae.InnerException else ex
| _ -> ex
printfn "%A" ex
exitCode <- 1
cancel()
System.Runtime.Loader.AssemblyLoadContext.Default.add_Unloading(fun _ -> cancel())
System.Console.CancelKeyPress.Add (fun args -> args.Cancel <- true; exitCode <- 2; cancel())
Async.StartWithContinuations(mainAsync(), (fun _ -> ()), exceptionHandler, (fun _ -> ()), cancelMainAsync.Token)
cancelMain.Wait()
exitCode
Aaron坚持认为Async.RunSchronously
与CancellationToken
兼容并且可以。我把他的答案标记为解决方案。除了OperationCancelException
以外,一切都会如我所愿。
open System
let mainAsync() =
async {
use precious = { new System.IDisposable with override this.Dispose() = printfn "safe haven" }
// blow up on purpose after a number of seconds
let seconds = 10
printfn "%d seconds and counting" seconds
do! (System.TimeSpan.FromSeconds(float seconds)).TotalMilliseconds |> int |> Async.Sleep
failwithf "kaboom!"
// the real app is has a System.TimeoutException being thrown from a C# Task
//wcfClient.RetrieveServiceContentAsync someValue |> Async.AwaitTask
return 0
}
[<EntryPoint>]
let main argv =
let cancellation = new Threading.CancellationTokenSource()
let cancel() =
if not cancellation.IsCancellationRequested then
cancellation.Cancel()
Runtime.Loader.AssemblyLoadContext.Default.add_Unloading(fun _ -> cancel())
Console.CancelKeyPress.Add (fun event -> event.Cancel <- true; cancel())
Async.RunSynchronously(mainAsync(), cancellationToken = cancellation.Token)
为完整起见,这是重新添加的其他异常处理。
open System
let mainAsync() =
async {
use precious = { new System.IDisposable with override this.Dispose() = printfn "safe haven" }
// blow up on purpose after a number of seconds
let seconds = 10
printfn "%d seconds and counting" seconds
do! (System.TimeSpan.FromSeconds(float seconds)).TotalMilliseconds |> int |> Async.Sleep
failwithf "kaboom!"
// the real app is has a System.TimeoutException being thrown from a C# Task
//wcfClient.RetrieveServiceContentAsync someValue |> Async.AwaitTask
return 0
}
[<EntryPoint>]
let main argv =
let cancellation = new Threading.CancellationTokenSource()
let cancel() =
if not cancellation.IsCancellationRequested then
cancellation.Cancel()
Runtime.Loader.AssemblyLoadContext.Default.add_Unloading(fun _ -> cancel())
Console.CancelKeyPress.Add (fun event -> event.Cancel <- true; cancel())
try
Async.RunSynchronously(mainAsync(), cancellationToken = cancellation.Token)
with
| :? OperationCanceledException -> 2
| ex ->
let ex =
match ex with
| :? AggregateException as ae ->
if ae.InnerExceptions.Count = 1 then ae.InnerException else ex
| _ -> ex
printfn "%A" ex
1