运行以下代码时出现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
工作流只有在需要使用我认为在这种情况下不需要的!
绑定时才是必需的,因为实际等待在特定函数中发生。
答案 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有addStuff
和updateStuff
创建自己的MyClient
个实例。
答案 1 :(得分:1)
在这种情况下async { ... }
块给你的是两件事:
use
关键字(即IDisposable将在嵌套addStuff
/ updateStuff
工作流执行后处理。)关于你使用的模式是否错误 - 是的,确实如此。
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>
值。然后调用者可以自由地执行任何他们想要的操作 - 执行它,忽略它,或者在不执行的情况下进一步传递它。
失败的原因是因为myClient
在doStuff
返回时被处理 - 这是在调用者有机会执行异步之前。
问题来自于这样一个事实,即你使用的这种模式将特定逻辑片段的执行分成两部分 - 一部分在调用函数时执行,另一部分在执行异步时执行,而代码是用意图编写的那里的一切都作为一个单元执行。
这种模式会引发或多或少的微妙错误,即使在很多情况下也不会容易观察到这种差异的影响 - 特别是如果异步在函数返回后立即无条件执行。