簡體   English   中英

將消息從異步線程發布到F#中的主線程

[英]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.CurrentAsync.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

我最終創建了一個EventSink ,它具有通過Drain()在主PowerShell線程上執行的回調隊列。 我將主要計算放在另一個線程上。 拉取請求具有完整的代碼和更多詳細信息。

在此處輸入圖片說明

在此處輸入圖片說明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM