gpt4 book ai didi

.net-core - 为什么在 F# 任务中引用此 IDisposable 时会被释放?

转载 作者:行者123 更新时间:2023-12-01 00:11:51 24 4
gpt4 key购买 nike

不久前,在我们的一个 (X) 单元测试中,我的一位同事写道:

[<Fact>]
let ``Green Flow tests`` () =
use factory = new WebAppFactory()
use client = factory.CreateClient()

Check.theGreenFlow client
|> Async.AwaitTask
|> Async.RunSynchronously

很惊讶,我想知道为什么我的同事强制我调用 Async.RunSynchronously而 XUnit 与 Task 一起工作正常和 Async类型相似。

然后我尝试了:

[<Fact>]
let ``Green Flow tests`` () =
use factory = new WebAppFactory()
use client = factory.CreateClient()

// this btw returns Task<unit>
Check.theGreenFlow client

并得到:
Rm.Bai.IntegrationTests.RetrievalWorkflow.Green Flow tests

System.AggregateException : One or more errors occurred. (One or more errors occurred. (One or more errors occurred. (One or more errors occurred. (One or more errors occurred. (One or more errors occurred. (Cannot access a disposed object.
Object name: 'IServiceProvider'.))))))

我就像“很公平, use 的范围在函数的底部结束,然后在 XUnit 运行器处理 Task<unit> 时释放”。

即使 IDisposable对象可能在返回 Task 的函数中被引用在上面的示例中,运行器在函数结束后运行任务,因此 Dispose根据我对 https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/resource-management-the-use-keyword 的理解,通话已经发生:

It provides the same functionality as a let binding but adds a call to Dispose on the value when the value goes out of scope. Note that the compiler inserts a null check on the value, so that if the value is null, the call to Dispose is not attempted.

[...]

when you use the use keyword, Dispose is called at the end of the containing code block



对我来说,避免处理对象的正确方法是包装一个计算表达式,如 taskasync计算表达式:

[<Fact>]
let ``Green Flow tests`` () =
task {
use factory = new WebAppFactory()
use client = factory.CreateClient()

do! Check.theGreenFlow client
}

Dispose()的那一刻实际上是代码是明确定义的。
并且不一定强制测试像在代码片段中那样同步运行。 1.

不像下面这样的东西,它仍然会导致 Cannot access a disposed object.错误:

[<Fact>]
let ``Green Flow tests`` () =
use factory = new WebAppFactory()
use client = factory.CreateClient()

async {
do! Check.theGreenFlow client |> Async.AwaitTask
}

这类似于片段号。 2.

我对这个问题的理解正确吗?

最佳答案

下一个实验演示了第二个示例的流程(当最后有 Check.theGreenFlow 客户端时)。 F#:

let tst(runSvc:Func<_,_>) =  
printfn "[%d] start tst" Thread.CurrentThread.ManagedThreadId
use srv = {new IDisposable with
override x.Dispose() =
printfn "[%d] srv disposed" Thread.CurrentThread.ManagedThreadId }
let res: Task = runSvc.Invoke srv
printfn "[%d] end tst" Thread.CurrentThread.ManagedThreadId
res

这是 Green Flow 测试的模拟。 C#:

static async Task runSvc(IDisposable svc) {
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] before runSvc");
await Task.Delay(1000);
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] after runSvc");
}
static async Task Main(string[] args)
{
var task = T1.tst(runSvc);
await task;
}

runSvc 扮演 Check.theGreenFlow 和 Main 的角色 - XUnit test run 的角色.

输出:
[1] start tst
[1] before runSvc
[1] end tst
[1] srv disposed
[4] after runSvc
  • 第一个线程开始测试并进入 Check.theGreenFlow。
  • 在某个点 Check.theGreenFlow 为异步操作注册了一个延续 - 在本例中,它在点 task.Delay 处。
  • 继续注册后,Check.theGreenFlow 立即返回对象任务(在我的示例中,继续打印“runSvc 之后”,这部分为以后注册)。
  • 当代码从测试函数返回时,服务对象被放置在最后,线程 1 等待结果,在某些时候将由线程池中注册的延续创建(另一个线程 4 在“runSvc 之后”执行)。如果线程 4 继续访问服务对象,则会发生对象处置异常。
  • 可能存在竞争条件。如果在我上面的示例中,您将 Thread.Sleep(2000) 放在“[%d] end tst”之前,则“在 runSvc 之后”将在处理之前继续执行,并且没有异常(exception)。
  • 关于.net-core - 为什么在 F# 任务中引用此 IDisposable 时会被释放?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57628184/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com