[英]Post messages from async threads to main thread in F#
有一個對observable的訂閱,它發送日志消息。 一些日志消息來自其他線程,因為它們位於F#異步塊中。 我需要能夠從主線程中寫出消息。
這是當前可過濾掉許多日志消息的代碼 ,因為它們不在主線程上:
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會抱怨。 我要解決這個錯誤嗎?
UPDATE 2015-06-16 Pue Tue 11:45 AM
@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
)
工作中的專家建議了我將嘗試的另一種解決方案,該方案涉及在訂閱時傳遞上下文。
UPDATE 2015-06-16 PUE Tue 5:30 PM
我得到了創建IObservable.SubscribeOn的幫助,該IObservable.SubscribeOn允許傳入SynchrnonizationContext。不幸的是,它不能解決所有問題,但可能是解決方案的一部分。 可能需要像SingleThreadSynchrnonizationContext這樣的自定義SynchronizationContext。 我很樂意幫助您制作一個,但在此之前,我將嘗試使用System.Reactive的Observable.ObserveOn(Scheduler.CurrentThread)
。
UPDATE 2015-06-16 PUE Tue 8:30 PM
我也無法使Rx正常工作。 Scheduler.CurrentThread
行為不符合我的期望。 然后,我嘗試了這些更改 ,但未調用回調。
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。 :/
對於UI應用程序(Forms,WPF,F#交互式,...),從試驗中選擇SynchronizationContext.Current
和Async.SwitchToContext
,並從Paket借用一些代碼就足夠了。
但是,對於控制台應用程序,由於沒有SynchronizationContext,因此沒有可以將繼續執行Post
到的線程,因此它們將最終出現在線程池中。 在MSDN博客上可以找到可能的解決方法。
僅適用於UI應用程序的解決方案:
有
module Logging
open System.Diagnostics
type Trace = { Level: TraceLevel; Text: string }
let public event = Event<Trace>()
let subscribe callback = Observable.subscribe callback event.Publish
和
[<AutoOpen>]
module CmdletExt
open System.Diagnostics
open System.Threading
type PSCmdletStandalone() =
member x.RegisterTrace() =
let syncContext = SynchronizationContext.Current
Logging.subscribe (fun trace ->
async {
do! Async.SwitchToContext syncContext
let threadId = Thread.CurrentThread.ManagedThreadId
match trace.Level with
| TraceLevel.Warning -> printfn "WARN (on %i): %s" threadId trace.Text
| TraceLevel.Error -> printfn "ERROR (on %i): %s" threadId trace.Text
| _ -> printfn "(on %i): %s" threadId trace.Text
} |> Async.Start // or Async.StartImmediate
)
然后,在主線程上注冊
#load "Logging.fs"
#load "SO.fs"
open System.Diagnostics
open System.Threading
open System
(new PSCmdletStandalone()).RegisterTrace()
printfn "Main: %i" Thread.CurrentThread.ManagedThreadId
for i in Enum.GetValues(typeof<TraceLevel>) do
async {
let workerId = Thread.CurrentThread.ManagedThreadId
do Logging.event.Trigger { Level = unbox i; Text = sprintf "From %i" workerId }
} |> Async.Start
產量例如
Main: 1
WARN (on 1): From 23
(on 1): From 25
ERROR (on 1): From 22
(on 1): From 13
(on 1): From 14
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.