Multithreading 如何从F中的非阻塞异步方法更新WPF GUI#

Multithreading 如何从F中的非阻塞异步方法更新WPF GUI#,multithreading,asynchronous,f#,elmish-wpf,Multithreading,Asynchronous,F#,Elmish Wpf,System.InvalidOperationException:'调用线程无法访问此对象,因为另一个线程拥有它。' 我有一个WPF GUI,其中有一个按钮,单击该按钮时会执行以下操作: 启动控件动画(在GUI上),然后 启动后台进程以获取本地打印机队列 我不想阻止主线程(GUI)。然而,当我试图用后台进程的结果更新主线程时,我的代码给出了上述错误 如何让后台异步进程更新主线程而不发生上下文冲突,并且不阻塞主线程 open System.Printing let GetPrinters =

System.InvalidOperationException:'调用线程无法访问此对象,因为另一个线程拥有它。'

我有一个WPF GUI,其中有一个按钮,单击该按钮时会执行以下操作:

  • 启动控件动画(在GUI上),然后
  • 启动后台进程以获取本地打印机队列
  • 我不想阻止主线程(GUI)。然而,当我试图用后台进程的结果更新主线程时,我的代码给出了上述错误

    如何让后台异步进程更新主线程而不发生上下文冲突,并且不阻塞主线程

    open System.Printing
    
    let GetPrinters = 
          let LocalPrintServer = new PrintServer()
          let printQueues = LocalPrintServer.GetPrintQueues [|EnumeratedPrintQueueTypes.Local; EnumeratedPrintQueueTypes.Connections|]
          let printerList =
              printQueues
                  |> Seq.cast<PrintQueue>
                  |> Seq.toList
          printerList
    
    
    let GetPrintersAsync() = 
           async { 
                   let! token = Async.StartChild(GetPrinters)
                   let! p = token
                   return p }
    
    编辑#1:以上是完整清单的简短版本。请看
    获取使用Elmish.wpf的完整源代码。谢谢。

    我还没有看过您的代码,但我认为WPF问题的基本答案是类。您还可以使用F#的
    Async.SwitchToContext
    。例如,请参阅问题。

    我还没有看过您的代码,但我认为WPF问题的基本答案是类。您还可以使用F#的
    Async.SwitchToContext
    。例如,请参阅问题。

    我现在有机会在GitHub上查看您的源代码,甚至运行它

    问题是打印队列是在异步函数中检索的,这意味着GUI线程之外的另一个线程。然后队列列表返回到GUI线程,并从那里进行访问。这就是为什么您会收到错误消息。当队列返回到GUI线程时,它们将映射到打印机类型。这太晚了。如果将该映射移到异步中,则不会太晚。返回到GUI线程的数据将是打印机列表,这可能没问题。至少没有撞车。我不能百分之百确定它是否正确,因为其中有一个PrintTicket类型的字段,问题是将它拉到另一个线程是否安全。如果您需要来自该对象的数据,可能也应该在返回到GUI线程之前将其映射到异步中的记录

    当我试图让它在没有错误的情况下运行时,这就是我最终得到的代码。我对异步也不是很了解,我也不确定在这种情况下使用异步是否有任何意义。但也许你只是在尝试一些东西

    | GetPrintersMsg ->
        let getPrinters () = async {
            use ps = new PrintServer()
            return
                ps.GetPrintQueues [| EnumeratedPrintQueueTypes.Local; EnumeratedPrintQueueTypes.Connections |]
                |> Seq.cast<PrintQueue>
                |> Seq.toList
                |> List.map (fun pq ->
                    {
                        Id = Guid.NewGuid()
                        fullname = pq.FullName
                        comment = pq.Comment
                        defaultPrintTicket = Some pq.DefaultPrintTicket
                        description = pq.Description
                        isInError = pq.IsInError
                        isOffline = pq.IsOffline
                    })
            }
        m, Cmd.OfAsync.either getPrinters () OnPrintersResult OnPrintersError
    | OnPrintersResult printers ->
        { m with Printers = printers; IsRefreshing = false }, Cmd.none
    
    | GetPrintersMsg->
    让getPrinters()=异步{
    使用ps=newprintserver()
    返回
    ps.GetPrintQueues[| EnumeratedPrintQueueTypes.Local;EnumeratedPrintQueueTypes.Connections |]
    |>序号
    |>序号:toList
    |>List.map(趣味pq->
    {
    Id=Guid.NewGuid()
    fullname=pq.fullname
    注释=pq.注释
    defaultPrintTicket=某些pq.defaultPrintTicket
    描述=pq.描述
    IsError=pq.IsError
    异弗林=pq.异弗林
    })
    }
    m、 Cmd.OfAsync.getPrinters()onPrinterResult onPrinterError
    |OnPrinterResult打印机->
    {m with Printers=Printers;IsRefreshing=false},Cmd.none
    
    我现在有机会在GitHub上查看您的源代码,甚至运行它

    问题是打印队列是在异步函数中检索的,这意味着GUI线程之外的另一个线程。然后队列列表返回到GUI线程,并从那里进行访问。这就是为什么您会收到错误消息。当队列返回到GUI线程时,它们将映射到打印机类型。这太晚了。如果将该映射移到异步中,则不会太晚。返回到GUI线程的数据将是打印机列表,这可能没问题。至少没有撞车。我不能百分之百确定它是否正确,因为其中有一个PrintTicket类型的字段,问题是将它拉到另一个线程是否安全。如果您需要来自该对象的数据,可能也应该在返回到GUI线程之前将其映射到异步中的记录

    当我试图让它在没有错误的情况下运行时,这就是我最终得到的代码。我对异步也不是很了解,我也不确定在这种情况下使用异步是否有任何意义。但也许你只是在尝试一些东西

    | GetPrintersMsg ->
        let getPrinters () = async {
            use ps = new PrintServer()
            return
                ps.GetPrintQueues [| EnumeratedPrintQueueTypes.Local; EnumeratedPrintQueueTypes.Connections |]
                |> Seq.cast<PrintQueue>
                |> Seq.toList
                |> List.map (fun pq ->
                    {
                        Id = Guid.NewGuid()
                        fullname = pq.FullName
                        comment = pq.Comment
                        defaultPrintTicket = Some pq.DefaultPrintTicket
                        description = pq.Description
                        isInError = pq.IsInError
                        isOffline = pq.IsOffline
                    })
            }
        m, Cmd.OfAsync.either getPrinters () OnPrintersResult OnPrintersError
    | OnPrintersResult printers ->
        { m with Printers = printers; IsRefreshing = false }, Cmd.none
    
    | GetPrintersMsg->
    让getPrinters()=异步{
    使用ps=newprintserver()
    返回
    ps.GetPrintQueues[| EnumeratedPrintQueueTypes.Local;EnumeratedPrintQueueTypes.Connections |]
    |>序号
    |>序号:toList
    |>List.map(趣味pq->
    {
    Id=Guid.NewGuid()
    fullname=pq.fullname
    注释=pq.注释
    defaultPrintTicket=某些pq.defaultPrintTicket
    描述=pq.描述
    IsError=pq.IsError
    异弗林=pq.异弗林
    })
    }
    m、 Cmd.OfAsync.getPrinters()onPrinterResult onPrinterError
    |OnPrinterResult打印机->
    {m with Printers=Printers;IsRefreshing=false},Cmd.none
    
    @BentTranberg实际上回答了这个问题的难点部分。我将此作为完整答案发布,因为编辑此问题似乎是多余的。下面的代码是Bent的答案,稍作修改。打印机现在在一个单独的线程上读取,如printfn语句所示:

    | GetPrintersMsg ->
        let getPrinters () = async {
            printfn "1: %i" Thread.CurrentThread.ManagedThreadId  
    
            let getprinters = async {
                printfn "11: %i" Thread.CurrentThread.ManagedThreadId  
                use ps = new PrintServer()
                return
                    ps.GetPrintQueues [| EnumeratedPrintQueueTypes.Local; EnumeratedPrintQueueTypes.Connections |]
                    |> Seq.cast<PrintQueue>
                    |> Seq.toList
                    |> List.map (fun pq ->
                        {
                            Id = Guid.NewGuid()
                            fullname = pq.FullName
                            comment = pq.Comment
                            defaultPrintTicket = Some pq.DefaultPrintTicket
                            description = pq.Description
                            isInError = pq.IsInError
                            isOffline = pq.IsOffline
                        }) }
            let! d = getprinters |> Async.StartChild
            return! d
            }
        m, Cmd.OfAsync.either getPrinters () OnPrintersResult OnPrintersError
    
    | GetPrintersMsg->
    让getPrinters()=异步{
    printfn“1:%i”Thread.CurrentThread.ManagedThreadId
    让getprinters=async{
    printfn“11:%i”Thread.CurrentThread.ManagedThreadId
    使用ps=newprintserver()
    返回
    ps.GetPrintQueues[| EnumeratedPrintQueueTypes.Local;EnumeratedPrintQueueTypes.Connections |]
    |