f#异步与c# async/await本质不同:task需用async.awaittask转为async,f# async则用async.startastask转为task;async不自动执行,须用async.start、async.runsynchronously或async.parallel并发;原生支持取消和异常处理,通过传入cancellationtoken及async.catch实现。

为什么不能直接在 F# 中用 C# 的 async/await
F# 的异步模型和 C# 本质不同:C# 的 Task 是基于状态机的可等待对象,而 F# 的 async { ... } workflow 返回的是 Async 类型——它本身不执行,只是描述异步计算的蓝图。直接把 C# 的 Task 当作 F# 的 Async 会编译失败或运行时挂起。
常见错误现象:error FS0001: This expression was expected to have type 'Async' but here has type 'Task'。
- 必须显式转换:用
Async.AwaitTask把Task转为Async - 反过来,若需从 F#
async得到Task(例如供 C# 调用),用Async.StartAsTask或Async.StartImmediateAsTask - 注意线程上下文:F#
Async默认不捕获同步上下文(如 UI 线程),而 C#await默认会——这在 WinForms/WPF 中可能引发跨线程异常
如何正确启动和组合 F# async 计算
F# 的 async { ... } 不会自动运行,必须显式触发。最常用方式是 Async.Start(fire-and-forget)、Async.RunSynchronously(阻塞等待,仅限顶层或测试)或转成 Task 后交由 C# 消费。
let fetchUrl (url: string) =
async {
use client = new System.Net.Http.HttpClient()
let! content = client.GetStringAsync(url) |> Async.AwaitTask
return content.Length
}
<p>// 启动多个并发请求(真正并行,非顺序)
let urls = [ "<a href="https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c">https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c</a>"; "<a href="https://www.php.cn/link/ef246753a70fce661e16668898810624">https://www.php.cn/link/ef246753a70fce661e16668898810624</a>" ]
let tasks = urls |> List.map fetchUrl |> Async.Parallel
let lengths = Async.RunSynchronously tasks // 等待全部完成,返回 int list-
Async.Parallel是并发核心:它把Async[]转成Async,内部自动调度,比手写Async.StartChild更简洁 - 避免误用
let!连续调用——那是串行;要并发必须先List.map构造多个Async,再统一Async.Parallel -
Async.Sequential存在但极少用:它按序执行列表里的Async,等价于手动let!链式调用
如何处理异常和取消(CancellationToken)
F# async 原生支持取消,但方式与 C# 不同:不是靠 token.ThrowIfCancellationRequested(),而是通过 Async.TryWith、Async.Catch 和绑定时传入 CancellationToken。
let riskyAsync (ct: System.Threading.CancellationToken) =
async {
do! Async.Sleep 1000
if ct.IsCancellationRequested then
return "cancelled"
else
return "done"
}
<p>// 启动时传入 token
let ct = new System.Threading.CancellationTokenSource(timeoutMs = 500)
let result = Async.RunSynchronously (riskyAsync ct.Token)- 所有内置异步操作(如
Async.Sleep、Async.AwaitTask)都自动响应CancellationToken,无需手动检查 - 用
Async.Catch捕获异常:Async.Catch myAsync返回Async<result exn>></result>,比 try/with 更函数式 - 不要在
async { ... }外层用 C# 风格的try...catch——它捕获不到异步内部抛出的异常
与 C# 互操作的关键点
混合项目中,F# 的 Async 经常需要被 C# 调用,或调用 C# 的 Task 方法。关键不是“怎么转”,而是“什么时候转、谁负责生命周期”。
- 从 C# 调用 F# 函数:F# 函数应返回
Task,用Async.StartAsTask包装:member _.GetData() = myAsync |> Async.StartAsTask - 在 F# 中调用 C# 方法:用
Async.AwaitTask,且注意是否需要.ConfigureAwait(false)(F# 默认已等效于ConfigureAwait(false),无需额外写) - 警惕死锁:在同步上下文(如 ASP.NET 同步控制器)中调用
Async.RunSynchronously可能卡住——务必改用StartAsTask+await流式处理
最易忽略的是取消传播:C# 传入的 CancellationToken 必须透传进 F# async 内部,否则 Async.AwaitTask 无法感知外部取消信号。











