如何从 F# 中的非阻塞异步方法更新 WPF GUI
How to update the WPF GUI from non-blocking async methods in F#
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
我有一个带有按钮的 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 }
这是我正在使用的更新程序:
let asyncUpper =
async {
let! printerQues = GetPrintersAsync ()
return printerQues
}
// This is where the error is being displayed.
let getprinters (printers:PrintQueue list) =
printers
|> List.map (fun pq -> {fullname = pq.FullName; comment = pq.Comment; defaultPrintTicket= Some pq.DefaultPrintTicket;
description= pq.Description; isInError=pq.IsInError; isOffline=pq.IsOffline; Id= Guid.NewGuid() } )
{ m with Printers = getprinters; IsRefreshing = false }
编辑 #1:以上是完整列表的简短版本。请参见
https://github.com/awaynemd/AsyncAndElmish 使用 Elmish.wpf 获得完整的源代码。谢谢。
我没有看过你的代码,但我认为你的 WPF 问题的基本答案是 Dispatcher
class. You can also use F#'s Async.SwitchToContext
. See 问题,例如。
我现在有机会在 GitHub 上查看您的来源,甚至 运行 它。
问题是打印队列是在异步函数中检索的,这意味着另一个线程而不是 GUI 线程。然后队列列表返回到 GUI 线程,并从那里访问。这就是您收到错误消息的原因。当队列返回到 GUI 线程时,它们将映射到打印机类型。这太晚了。如果您将该映射移到异步中,则不会太晚。返回到 GUI 线程的数据将是打印机列表,这可能没问题。至少没有崩溃。我不能百分百确定它是否可以,因为那里有一个 PrintTicket 类型的字段,问题是将它拉到另一个线程是否安全。如果您需要来自该对象的数据,也许这也应该在返回到 GUI 线程之前映射到异步中的记录。
在尝试 运行ning 时没有错误,这是我最终得到的代码。我也不太了解异步,而且我不确定在这种情况下使用异步是否有任何意义。但也许你只是在尝试一些东西。
| 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
@BentTranberg 实际上回答了这个问题的难点部分。我 post 这是完整的答案,因为编辑问题似乎是多余的。下面的代码是经过一些修改的 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
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
我有一个带有按钮的 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 }
这是我正在使用的更新程序:
let asyncUpper =
async {
let! printerQues = GetPrintersAsync ()
return printerQues
}
// This is where the error is being displayed.
let getprinters (printers:PrintQueue list) =
printers
|> List.map (fun pq -> {fullname = pq.FullName; comment = pq.Comment; defaultPrintTicket= Some pq.DefaultPrintTicket;
description= pq.Description; isInError=pq.IsInError; isOffline=pq.IsOffline; Id= Guid.NewGuid() } )
{ m with Printers = getprinters; IsRefreshing = false }
编辑 #1:以上是完整列表的简短版本。请参见 https://github.com/awaynemd/AsyncAndElmish 使用 Elmish.wpf 获得完整的源代码。谢谢。
我没有看过你的代码,但我认为你的 WPF 问题的基本答案是 Dispatcher
class. You can also use F#'s Async.SwitchToContext
. See
我现在有机会在 GitHub 上查看您的来源,甚至 运行 它。
问题是打印队列是在异步函数中检索的,这意味着另一个线程而不是 GUI 线程。然后队列列表返回到 GUI 线程,并从那里访问。这就是您收到错误消息的原因。当队列返回到 GUI 线程时,它们将映射到打印机类型。这太晚了。如果您将该映射移到异步中,则不会太晚。返回到 GUI 线程的数据将是打印机列表,这可能没问题。至少没有崩溃。我不能百分百确定它是否可以,因为那里有一个 PrintTicket 类型的字段,问题是将它拉到另一个线程是否安全。如果您需要来自该对象的数据,也许这也应该在返回到 GUI 线程之前映射到异步中的记录。
在尝试 运行ning 时没有错误,这是我最终得到的代码。我也不太了解异步,而且我不确定在这种情况下使用异步是否有任何意义。但也许你只是在尝试一些东西。
| 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
@BentTranberg 实际上回答了这个问题的难点部分。我 post 这是完整的答案,因为编辑问题似乎是多余的。下面的代码是经过一些修改的 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