簡體   English   中英

WinForms App中的F#跨線程UI異常

[英]F# cross-thread UI exception in WinForms App

我在使用F#開發一個簡單的應用程序時遇到問題,它只讀取所請求的HTML頁面的長度。

當您開發UI應用程序時,似乎這樣的錯誤對於VB.NET / C#語言也是類似的。

在此輸入圖像描述

但我對F#很陌生,並且真的沒想象會在F#中解決這個問題。

F#中的源代碼:

http://pastebin.com/e6WM0Sjw

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.

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