【发布时间】:2015-06-18 09:49:29
【问题描述】:
订阅了一个发送日志消息的可观察对象。一些日志消息来自其他线程,因为它们位于 F# 异步块中。我需要能够从主线程中写出消息。
这里是the code,它目前过滤掉了许多日志消息,因为它们不在主线程上:
member x.RegisterTrace() =
Logging.verbose <- x.Verbose
let id = Threading.Thread.CurrentThread.ManagedThreadId
Logging.subscribe (fun trace ->
if id = Threading.Thread.CurrentThread.ManagedThreadId then
match trace.Level with
| TraceLevel.Warning -> x.WriteWarning trace.Text
| TraceLevel.Error -> x.WriteWarning trace.Text
| _ -> x.WriteObject trace.Text
else
Diagnostics.Debug.Write(sprintf "not on main PS thread: %A" trace)
)
我有多种使用System.Threading.SynchronizationContent.Current、.SetSynchronizationConent、.Send、.Post的形式。我还涉足System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext。我也试过Async.SwitchToContext。无论我做什么,System.Threading.Thread.CurrentThread.ManagedThreadId 最终都会变得不同,PowerShell 会抱怨。我是不是搞错了?
这是正在进行的工作 pull request 以及有关 the problem 的更多详细信息。
更新时间:太平洋标准时间 2015 年 6 月 16 日星期二上午 11:45
@RCH 谢谢,但使用Async.SwitchToContext 设置SynchronizationContext 似乎不起作用。这是我做Paket-Restore -Force时的代码和调试输出:
member x.RegisterTrace() =
let a = Thread.CurrentThread.ManagedThreadId
Logging.verbose <- x.Verbose
let ctx = SynchronizationContext.Current
Logging.subscribe (fun trace ->
let b = Thread.CurrentThread.ManagedThreadId
async {
let c = Thread.CurrentThread.ManagedThreadId
do! Async.SwitchToContext ctx
let d = Thread.CurrentThread.ManagedThreadId
Debug.WriteLine (sprintf "%d %d %d %d %s" a b c d trace.Text)
} |> Async.Start
)
一位工作专家推荐了另一种解决方案,我将尝试在订阅时传递上下文。
更新时间:太平洋标准时间 2015 年 6 月 16 日星期二下午 5:30
我得到了创建IObservable.SubscribeOn 的帮助,该IObservable.SubscribeOn 允许传入SynchrnonizationContext。不幸的是,它也不能解决问题,但可能是解决方案的一部分。可能需要像 SingleThreadSynchrnonizationContext 这样的自定义 SynchronizationContext。我很乐意帮助制作一个,但在此之前,我将尝试 System.Reactive 的 Observable.ObserveOn(Scheduler.CurrentThread)。
更新时间:太平洋标准时间 2015 年 6 月 16 日星期二晚上 8:30
我也无法让 Rx 工作。 Scheduler.CurrentThread doesn't behave 我希望的方式。然后我尝试了these changes 并且没有调用回调。
member x.RegisterTrace() =
Logging.verbose <- x.Verbose
let a = Threading.Thread.CurrentThread.ManagedThreadId
let ctx = match SynchronizationContext.Current with null -> SynchronizationContext() | sc -> sc
let sch = SynchronizationContextScheduler ctx
Logging.event.Publish.ObserveOn sch
|> Observable.subscribe (fun trace ->
let b = Threading.Thread.CurrentThread.ManagedThreadId
Debug.WriteLine(sprintf "%d %d %s" a b trace.Text)
可能需要自定义 SynchronizationContext。 :/
【问题讨论】:
-
啊,终于。应该从一开始就想到:控制台应用程序没有 SynchronizationContext,所以 SynchronizationContext.Current 始终为 null。 -> 查看编辑后的答案。
-
是的,如果将 Rx 作为新的依赖项不是问题,那也是我的首选方式。除了
Event,Rx 还提供了非常方便的Subjects。 -
我想我可以将依赖项添加到 Paket.PowerShell,但不能添加到 Paket.Core。不幸的是,它也不起作用。我发表了我的尝试。
-
太好了,很高兴最终成功了!在有人想出更好的方法之前,我至少可以建议对
QueuingSynchronizationContext进行一些微小的改进:gist.github.com/rasch/f55d5205730f97e32932 -
我喜欢这些改进,并将在我的下一次迭代中使用它们。
标签: .net powershell f# system.reactive f#-async