视图模型中的 F# async/await

F# async/await in a viewmodel

F# 中的我的 ViewModel

我正在尝试使用 F# 而不是 C# 来实现我的 ViewModel。 我正在关注这个 article(顺便说一句,有什么更新的或更好的建议吗?)。

所以假设我有我的视图模型基础实现(MVVM.ViewModel,它在 C# 中,但我可以从 F# 中引用它)和一个简单的 Status 属性.

namespace FuncViewModel
open MVVM.ViewModel
open System

    type MyFuncViewModel() = 
        inherit ViewModelBase()

        let mutable status=""

        member this.RunSetStatus() =
            status <- "Reset @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
            base.OnPropertyChanged("Status")

        member this.SetStatus = new DelegateCommand(fun _ -> this.RunSetStatus() )


    member this.Status 
        with get() =
            status
        and set(value) =
             status <- value
             base.OnPropertyChanged(fun () -> this.Status)

一切都按预期工作,到目前为止一切顺利(但如果您发现任何概念性错误或者如果您为上述代码找到更惯用的版本,请告诉我)

介绍 async/await 模式

这就是我出错的地方:我知道如何在 C# 中执行此操作,但我不擅长在 F# 中执行此操作。

我试过以下方法。

member this.RunSetStatus() =
    status <- "Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
    base.OnPropertyChanged("Status")
    let task = async {

        do! Async.Sleep (30 * 1000) 

    }
    Async.StartImmediate(task)
    status <- "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
    base.OnPropertyChanged("Status")

问题是 - 当我 运行 完整的 WPF 应用程序时 - 我看不到预期的延迟:最终状态直接进入输出。

如果我把上面的Async.StartImmediate(task)改成Async.RunSynchronously(task),当然我看到了进度的延迟,但是应用程序被冻结了,所以这不是我想要的。

如果我将其重新排列为

member this.RunSetStatus() =
    status <- "Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
    base.OnPropertyChanged("Status")
    let task = async {

        do! Async.Sleep (30 * 1000) 

        status <- "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
        base.OnPropertyChanged("Status")

    }
    Async.StartImmediate(task)

我收到一个错误

The member or object constructor 'OnPropertyChanged' is not accessible. Private members may only be accessed from within the declaring type. Protected members may only be accessed from an extending type and cannot be accessed from inner lambda expressions.

编辑(续)

最后,这个我也试过了

member this.RunSetStatus() =
    status <- "Start resetting @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
    base.OnPropertyChanged("Status")
    let task = async {

        do! Async.Sleep (30 * 1000) 

    }
    Async.StartWithContinuations(task, 
        (fun _ -> this.Status <- "Reset done @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"),
        (fun _ -> this.Status <- "Operation failed."),
        (fun _ -> this.Status <- "Operation canceled."))

但应用程序崩溃并出现 ArgumentException

堆栈跟踪

Application: MyFuncWPF.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.ArgumentException Stack: at MVVM.ViewModel.ViewModelBase.OnPropertyChanged[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089] at FuncViewModel.MyFuncViewModel.set_Status(System.String) at .$MyVMLib+RunSetStatus@20.Invoke(Microsoft.FSharp.Core.Unit) at Microsoft.FSharp.Control.CancellationTokenOps+StartWithContinuations@1274[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].Invoke(System.__Canon) at .$Control.loop@430-52(Microsoft.FSharp.Control.Trampoline, Microsoft.FSharp.Core.FSharpFunc2<Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Control.FakeUnitValue>) at Microsoft.FSharp.Control.Trampoline.ExecuteAction(Microsoft.FSharp.Core.FSharpFunc2) at .$Control+-ctor@507.Invoke(System.Object) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object

编辑 2 - 发现问题

我不得不使用以下 - 更简单 - OnPropertyChanged 的重载(它们都按照此 source code 在 C# 中实现和工作)

    member this.Status 
        with get() =
            status
        and set(value) =
             status <- value
             base.OnPropertyChanged("Status")

异常的原因是F#的函数不能表示为MemberExpression:

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        MemberExpression body = selectorExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("The body must be a member expression");
        OnPropertyChanged(body.Member.Name);
    }

在调试器中,您会看到您得到的异常实际上是 - "The body must be a member expression"。

您的第一个代码:

member this.RunSetStatus() =
    status <- "Reset @" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
    base.OnPropertyChanged("Status")

有效,因为您没有使用 属性 Status 的 setter。

所以你需要使用不同的重载。