[英]A random cross-thread operation exception for Winforms multithreaded UI operation
[英]F# cross-thread UI exception in WinForms App
我在使用F#開發一個簡單的應用程序時遇到問題,它只讀取所請求的HTML頁面的長度。
當您開發UI應用程序時,似乎這樣的錯誤對於VB.NET / C#語言也是類似的。
但我對F#很陌生,並且真的沒想象會在F#中解決這個問題。
F#中的源代碼:
open System
open System.Net
open Microsoft.FSharp.Control.WebExtensions
open System.Windows.Forms
let form = new Form()
let text = new Label()
let button = new Button()
let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
"MSDN", "http://msdn.microsoft.com/"
"Bing", "http://www.bing.com"
]
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
text.Text <- String.Format("Read %d characters for %s", html.Length, name)
with
| ex -> printfn "%s" (ex.Message);
}
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
form.Width <- 400
form.Height <- 300
form.Visible <- true
form.Text <- "Test download tool"
text.Width <- 200
text.Height <- 50
text.Top <- 0
text.Left <- 0
form.Controls.Add(text)
button.Text <- "click me"
button.Top <- text.Height
button.Left <- 0
button.Click |> Event.add(fun sender -> runAll() |> ignore)
form.Controls.Add(button)
[<STAThread>]
do Application.Run(form)
最好的祝福,
謝謝!
在更新text.Text
屬性之前,必須將線程上下文從Async ThreadPool
切換到UI線程。 有關F#Async特定說明,請參閱MSDN鏈接 。
通過捕獲UI上下文修改您的代碼段后
let uiContext = System.Threading.SynchronizationContext()
放在let form = new Form()
語句fetchAsync
,並將fetchAsync
定義更改為
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
do! Async.SwitchToContext(uiContext)
text.Text <- text.Text + String.Format("Read {0} characters for {1}\n", html.Length, name)
with
| ex -> printfn "%s" (ex.Message);
}
它沒有任何問題。
更新:在與一位強調需要干凈地操縱UI上下文的同事討論調試器特性之后,以下修改現在對於運行方式是不可知的:
open System
open System.Net
open Microsoft.FSharp.Control.WebExtensions
open System.Windows.Forms
open System.Threading
let form = new Form()
let text = new Label()
let button = new Button()
let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
"MSDN", "http://msdn.microsoft.com/"
"Bing", "http://www.bing.com"
]
let fetchAsync(name, url:string, ctx) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
do! Async.SwitchToContext ctx
text.Text <- text.Text + sprintf "Read %d characters for %s\n" html.Length name
with
| ex -> printfn "%s" (ex.Message);
}
let runAll() =
let ctx = SynchronizationContext.Current
text.Text <- String.Format("{0}\n", System.DateTime.Now)
urlList
|> Seq.map (fun(site, url) -> fetchAsync(site, url, ctx))
|> Async.Parallel
|> Async.Ignore
|> Async.Start
form.Width <- 400
form.Height <- 300
form.Visible <- true
form.Text <- "Test download tool"
text.Width <- 200
text.Height <- 100
text.Top <- 0
text.Left <- 0
form.Controls.Add(text)
button.Text <- "click me"
button.Top <- text.Height
button.Left <- 0
button.Click |> Event.add(fun sender -> runAll() |> ignore)
form.Controls.Add(button)
[<STAThread>]
do Application.Run(form)
作為顯式使用Invoke
操作(或上下文切換)的替代方法,您還可以使用Async.StartImmediate
開始計算。
此原語在當前線程(同步上下文)上啟動異步工作流,然后確保在同一個同步上下文中調用所有連續 - 因此它實質上為您自動處理Invoke
。
為此,您無需在fetchAsync
更改任何fetchAsync
。 您只需要在runAll
更改計算的啟動runAll
:
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.Ignore
|> Async.StartImmediate
就像之前一樣,它並行地組合它們,然后忽略結果以獲得Async<unit>
類型的計算,然后在當前線程上啟動它。 這是F#async中的一個很好的功能:-)
我遇到了同樣的問題,因為Async.SwitchToContext沒有切換到Main Gui線程。 實際上它正在切換到其他一些線程。
最后我發現問題在於我是如何獲得uiContext的。 使用以下工作:
let uiContext = System.Threading.SynchronizationContext.Current
但它不起作用:
let uiContext = System.Threading.SynchronizationContext()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.