在F#中使用IDisposable时是否需要使用async关键字?

时间:2018-02-06 17:38:24

标签: wcf f# async-await idisposable

运行以下代码时出现Cannot access a disposed object错误(MyClient是由C#项目中的服务引用生成的WCF客户端。)

type Action =
    | Add
    | Update

let addStuff (myClient:MyClient) arg = async {
    let! response = myClient.AddAsync arg |> Async.AwaitTask
    return response.ID
}

let updateStuff (myClient:MyClient) arg = async {
    let! response = myClient.UpdateAsync arg |> Async.AwaitTask
    return response.ID
}

let doStuff arg =
    use myClient = new MyClient()
    match arg.Action with
    | Add -> arg |> addStuff myClient
    | Update -> arg |> updateStuff myClient

let args = [Add, Add, Update, Add]

let results =
    args
    |> List.map doStuff
    |> Async.Parallel

在我预期之前,客户似乎正在处理。如果我将doStuff更改为:

let doStuff arg = async {
    use myClient = new MyClient()
    return!
        match arg.Action with
        | Add -> arg |> addStuff myClient
        | Update -> arg |> updateStuff myClient
}

两个函数的返回类型是Async<int>。为什么客户在第一个例子的早期处理?我认为这两个例子在逻辑上是相同的。我的理解是async工作流只有在需要使用我认为在这种情况下不需要的!绑定时才是必需的,因为实际等待在特定函数中发生。

2 个答案:

答案 0 :(得分:4)

问题在于doStuff

let doStuff arg =
    use myClient = new MyClient()
    match arg.Action with
    | Add -> arg |> addStuff myClient
    | Update -> arg |> updateStuff myClient

您正在将myClient传递给捕获MyClient实例的异步函数。但是,当doStuff返回时,它会调用Dispose实例上的MyClient并释放客户端。当您的异步方法开始运行时,它正在使用已处理的实例。

使doStuff有效,因为dispose成为异步工作流的一部分。

另一个选择是不use MyClient个实例,但instean有addStuffupdateStuff创建自己的MyClient个实例。

答案 1 :(得分:1)

在这种情况下async { ... }块给你的是两件事:

  1. 它会延迟执行其中的所有代码,直到执行异步计算为止,
  2. 相应地处理use关键字(即IDisposable将在嵌套addStuff / updateStuff工作流执行后处理。)
  3. 关于你使用的模式是否错误 - 是的,确实如此。

    F#async和C#async-await是两种非常不同的结构,具有非常不同的语义,并且一个人的经验不容易携带到另一个。

    C#async-await是一种链接Tasks的方式。 Task<'t>是未来,即类型为't的值的容器,以后可用。默认情况下,生成该值的计算会立即调度以在后台线程上执行,访问该值是一个阻塞操作,直到该计算完成,并且进一步尝试访问它将返回缓存值。

    另一方面,F#Async<'t>是表示计算的抽象规范的值,一旦执行,就会产生类型't的值。然而,执行被推迟到调用者 - 谁可以选择如何(以及如果有的话)执行计算。与任务不同,async不带有类型't的值 - 每次执行都会产生一个新的(可能不同的)值。

    回到你的样本:

    let doStuff arg =
        use myClient = new MyClient()
        match arg.Action with
        | Add -> arg |> addStuff myClient
        | Update -> arg |> updateStuff myClient
    

    这里有一个函数可以完成一些工作并向调用者返回Async<'t>值。然后调用者可以自由地执行任何他们想要的操作 - 执行它,忽略它,或者在不执行的情况下进一步传递它。

    失败的原因是因为myClientdoStuff返回时被处理 - 这是在调用者有机会执行异步之前。

    问题来自于这样一个事实,即你使用的这种模式将特定逻辑片段的执行分成两部分 - 一部分在调用函数时执行,另一部分在执行异步时执行,而代码是用意图编写的那里的一切都作为一个单元执行。

    这种模式会引发或多或少的微妙错误,即使在很多情况下也不会容易观察到这种差异的影响 - 特别是如果异步在函数返回后立即无条件执行。