1

在我的F# (FsXaml/Code Behind)应用程序中,我想使用进度条而不像在 C# 中那样使用后台工作程序。根据互联网上的一篇文章(链接在这里),我尝试使用异步工作流。

我基于上述文章中的示例(在某种程度上)创建了代码,但它并没有像我预期的那样工作。当前线程(UI 线程)仍然被阻塞,就好像那里没有异步代码一样。不会切换到后台线程。只有在长时间运行的操作完成后才会激活进度条。删除onThreadPool函数没有任何效果。

我的问题是:我的代码有什么问题以及如何改正?

type MainWindowXaml = FsXaml.XAML<"XAMLAndCodeBehind/MainWindow.xaml">

type MainWindow() as this =

    inherit MainWindowXaml()

    //....some code....

    let ButtonClick _ = 
   
        //....some code....
       
        let longRunningOperation() = //....some long running operation (reading from Google Sheets)....            
             
        let progressBar() = this.ProgressBar.IsIndeterminate <- true     

        let doBusyAsync progress operation =  
            progress
            async
                {   
                  do! operation
                }
            |> Async.StartImmediate 
    
        let onThreadPool operation =
            async
                {    
                  let context = System.Threading.SynchronizationContext.Current
                  do! Async.SwitchToThreadPool()
                  let! result = operation
                  do! Async.SwitchToContext context
                  return result
                } 
    
        let asyncOperation progress operation =   
            async { operation } 
            |> onThreadPool
            |> doBusyAsync progress 
    
        (progressBar(), longRunningOperation()) ||> asyncOperation 
      
    do
        //....some code....
        this.Button.Click.Add ButtonClick
4

2 回答 2

3

您的代码有很多问题。

  • 首先,progressBar(), longRunningOperation()你实际上调用了长时间运行的操作,所以它都在这里运行。(据我从您不完整的示例中可以猜到,这只是一个函数调用,而不是另一个异步操作)。

  • 然后你传递结果operationprogress四处传递,但这些只是unit实际上没有做任何事情的值。

  • async { operation }因此,您传递给的异步操作onThreadPool根本不做任何事情。

  • doBusyAsync中,您使用Async.StartImmediate以阻塞方式运行操作(因此这会阻塞线程,即使它正在运行一些实际操作)。

  • 除了阻塞之外,你也不需要async { do! operation },因为这相当于 just operation

总之,您的代码不知何故变得过于复杂。作为第一步,您应该将其简化为非常基本的内容。我没有正确的设置来尝试这个,但我认为类似下面的东西应该可以解决问题:

let ButtonClick _ = 
  let longRunningOperation() = 
    // some long-running operation

  let asyncOperation() = async {
    // Start the progress bar here
    let context = System.Threading.SynchronizationContext.Current
    do! Async.SwitchToThreadPool()
    let result = longRunningOperation()
    do! Async.SwitchToContext context
    // Display the 'result' in your user interface
    // Stop the progress bar here
  }

  Async.Start(asyncOperation)

我删除了所有无用的函数和参数传递,并尽可能地简化了它——这只是你长时间运行的操作,async一旦切换到线程池就会直接调用它。你得到你的结果,并且在切换回原始上下文后,应该能够在你的用户界面中显示它。理想情况下,您应该使longRunningOperation自身异步(并使用 调用它let!),但以上应该可以工作。

于 2021-11-24T00:15:43.350 回答
0

总而言之,我根据 Jim Foye 的评论(关于跳回 UI 线程)使用与长时间运行操作相关的代码扩展了 Tomáš Petříček 的代码。代码现在就像一个魅力。感谢 Tomáš Petříček 的友好和详细的回答。

    let low = string (this.TextBox2.Text)
    let high = string (this.TextBox3.Text)
    let path = string (this.TextBox4.Text)

    (* longRunningOperation() replaced by textBoxString4() and textBoxString3() 
       based on the comment by Jim Foye
    
    let longRunningOperation() = 
        async
            {
              match textBoxString4 low high >= 0 with
              | false -> this.TextBox1.Text <- textBoxString3 low high path 
              | true  -> this.TextBox1.Text <- "Chybný rozdíl krajních hodnot"        
            }
    *)

    let textBoxString4() = 
        async
            {
              let result = textBoxString4 low high
              return result
            }                  
                           
    let textBoxString3() =        
        async
            {
              //the actual long running operation (reading data 
              //from Google Sheets)
              let result = textBoxString3 low high path 
              return result
            }  

    let asyncOperation() = 
        async
            {
              let context = System.Threading.SynchronizationContext.Current
              this.ProgressBar2.IsIndeterminate <- true
              do! Async.SwitchToThreadPool()
              (*let! result = longRunningOperation() throws an exception 
              "The calling thread cannot access this object because
               a different thread owns it." 
              *)
              let! result4 = textBoxString4()  
              let! result3 = textBoxString3()  
              do! Async.SwitchToContext context
              match result4 >= 0 with
              | false -> this.TextBox1.Text <- result3
              | true  -> this.TextBox1.Text <- "Chybný rozdíl krajních hodnot" 
              this.ProgressBar2.IsIndeterminate <- false
            } 
    Async.StartImmediate(asyncOperation())//not working with Async.Start
                                             
于 2021-11-24T03:38:34.010 回答