我正在FSharp的taks CE中对一个长时间运行的操作进行编码,如下所示
let longRunningTask = Task.Run(...)
// Now let's do the rest of the multi-tasking program
task {
DO SOMETHING
let! result = longRunningTask
DO SOMETHING ELSE
}
问题是DO SOMETHING ELSE似乎在任意线程上运行(也可以通过打印当前线程id来观察(,而我绝对需要它在与DO SOMETHING相同的线程上运行,因为除了longRunningTask之外,我不想要任何其他形式的并发。
我已经尝试了多种方式来设置当前同步上下文,首先创建一个该类型的唯一值,但这似乎不会影响结果。
这可能有些过头了,但SynchronizationContext可能会对您有所帮助。它用于将委托分派到一些线程。关于它的工作原理有很多解释(搜索ConfigureAwait(false)
(,所以我将重点讨论的实现
type ThreadOwningSyncCtx() =
inherit SynchronizationContext()
let _queue = new BlockingCollection<(SendOrPostCallback * obj)>()
member _.DoWork(cancellationToken: CancellationToken) =
while not cancellationToken.IsCancellationRequested do
let (callback, state) = _queue.Take()
callback.Invoke state
()
override _.Post(callback, state) =
_queue.Add((callback, state))
()
override _.Send(callback, state) =
let tcs = TaskCompletionSource()
let cb s =
callback.Invoke s
tcs.SetResult()
_queue.Add((cb, state))
tcs.Task.Wait()
()
方法说明:
Post
:在异步路径上执行的方法。当C#await
或F#let!
do!
异步完成时,从Task
的基础结构调用此方法。回调排队等待某个时间完成。Send
:在同步路径上执行的方法。预计callback
将在该方法返回之前执行。例如,当有人调用CancellationTokenSource.Cancel
或WPF的Dispatcher.Invoke
或WinFormsControl.Invoke
时DoWork
:阻止当前线程执行所有挂起回调的方法,因为我们不能只是中断线程执行某些任务,它必须在等待它。
用法:
let syncCtx = ThreadOwningSyncCtx()
// set current sync ctx, so every continuation is queued back to main thread.
// comment this line and `printThreadId` will return different numbers
SynchronizationContext.SetSynchronizationContext syncCtx
let printThreadId() =
printfn "%d" Thread.CurrentThread.ManagedThreadId
// create cancellation token, so app won't run indefinitely
let cts = new CancellationTokenSource()
// task to simulate some meaningful work
task {
printThreadId()
do! Task.Yield() // this action always completes asynchronously
printThreadId()
cts.Cancel() // cancel token, so main thread can continue it's work
} |> ignore
// process all pending continuations
syncCtx.DoWork(cts.Token)
如果你真的需要确保主计算发生在一个线程上,你可以完全避免计算表达式:
printfn "Do something (on thread %A)" Thread.CurrentThread.ManagedThreadId
let task = startLongRunningTask ()
let result = task.Result
printfn "Do something else (on the same thread %A)" Thread.CurrentThread.ManagedThreadId
请注意,Result
会阻塞调用线程,直到任务完成,这似乎是您想要的行为。(更简单的是:您也可以在主线程上运行长时间运行的任务,但我认为这是不可取的。(