如何让F# Task CE中let!后的代码与前置代码同线程执行?
F# Task计算表达式中确保代码运行在同一线程的解决方案
问题原因
当你用Task.Run启动长时任务,再在task CE中用let!等待它时,后续代码的执行线程由当前同步上下文决定:
- UI程序(WPF/WinForms)自带同步上下文,会自动把后续代码调度回UI线程;
- 控制台程序默认没有同步上下文,
let!会将后续代码分配到线程池的任意线程,导致DO SOMETHING ELSE和DO SOMETHING不在同一线程。
解决方案
方案1:自定义单线程同步上下文(适合控制台/无默认上下文场景)
实现一个单线程同步上下文,让整个task CE的代码(除长时任务本身)都在同一个线程执行:
首先定义同步上下文类:
open System open System.Threading type SingleThreadSynchronizationContext() = inherit SynchronizationContext() let workQueue = Queue<SendOrPostCallback * obj>() let mutable keepRunning = true override _.Post(callback, state) = lock workQueue (fun () -> workQueue.Enqueue(callback, state)) override _.Send(callback, state) = callback.Invoke(state) // 启动消息循环,处理队列中的任务 member _.StartLoop() = while keepRunning do let callback, state = lock workQueue (fun () -> workQueue.Dequeue()) callback.Invoke(state) // 停止消息循环 member _.StopLoop() = keepRunning <- false
用这个上下文包裹整个程序流程:
let main () = let singleThreadCtx = SingleThreadSynchronizationContext() // 设置当前线程的同步上下文 SynchronizationContext.SetSynchronizationContext(singleThreadCtx) // 长时任务,运行在线程池线程 let longRunningTask = Task.Run(fun () -> Thread.Sleep(1000) "Long task finished") let programTask = task { // DO SOMETHING - 运行在单线程上下文的线程 printfn $"DO SOMETHING on thread ID: {Thread.CurrentThread.ManagedThreadId}" let! result = longRunningTask // DO SOMETHING ELSE - 自动回到单线程上下文的线程 printfn $"DO SOMETHING ELSE on thread ID: {Thread.CurrentThread.ManagedThreadId}, Result: {result}" } // 任务完成后停止消息循环 programTask.ContinueWith(fun _ -> singleThreadCtx.StopLoop()) |> ignore // 启动单线程消息循环 singleThreadCtx.StartLoop() main()
方案2:手动捕获上下文并调度(局部场景)
如果不需要全局单线程,可手动捕获DO SOMETHING所在的上下文,在let!后将后续代码调度回该上下文:
open System open System.Threading let main () = let longRunningTask = Task.Run(fun () -> Thread.Sleep(1000) "Long task finished") let programTask = task { // 捕获当前执行的同步上下文 let currentCtx = SynchronizationContext.Current printfn $"DO SOMETHING on thread ID: {Thread.CurrentThread.ManagedThreadId}" let! result = longRunningTask // 手动将后续代码调度回之前的上下文 do! Task.Factory.StartNew(fun () -> // DO SOMETHING ELSE - 运行在DO SOMETHING的同一线程 printfn $"DO SOMETHING ELSE on thread ID: {Thread.CurrentThread.ManagedThreadId}, Result: {result}" , CancellationToken.None , TaskCreationOptions.None , currentCtx) } programTask.Wait() main()
注意事项
- UI程序无需自定义上下文,框架自带的同步上下文会自动处理线程调度;
- 之前设置同步上下文无效,大概率是因为没有启动对应的消息循环,或者上下文被后续操作覆盖。
内容的提问来源于stack exchange,提问作者vincenzoml
相关产品推荐
相关产品推荐

