我的代码中遇到了一些暂时的死锁,无法理解它。
简单代码(我无法创建简单的调用链来重现 InvokeChangeEvent
中的代码)
[Test]
public async void Test()
{
sut.InvokeChangeEvent("./foo.file");
// Event is handled by an async handler chaining multiple await resulting in a file write
// await Task.Delay(3000);
Assert.That(() => Directory.GetFiles("some dir").Count(), Is.EqualTo(3).After(15000, 300));
}
我知道你们 (:D) 想要可执行代码,但我无法将其分割,因此我希望通过解释获得一些见解。
会发生什么:sut.InvokeChangeEvent
调用一个事件处理程序,该事件处理程序随后调用一个async
事件处理程序,然后该事件处理程序又调用一些async
。链的末尾产生一个 Task.Run
,它归结为写入 3 个文件。
上面的 Assert 是作为带有 After
的委托(delegate)实现的,它返回一个 DelayedConstraint
并且有一个非常大的最大时间(15 秒)和一个小的轮询间隔。
现在,当我调试代码时,InvokeChangeEvent 调用完全执行到最后一个 Task.Run,但是当 Task.Run 返回时,执行将返回到主线程,并且执行断言,进入“等待轮询” .
但是断言永远不会成功。当我调试问题时,总是在 Assert 委托(delegate)运行(并失败)之后处理 Task.Run 的返回。
我发现,当我在断言之前放置 await Task.Delay(3000);
时,代码就会正确执行。
正如前面提到的,测试中的系统有大量的等待和 Task.Runs 链接,我无法使用一些简单的可运行代码重现该问题。
我已经搜索了一段时间,但我无法弄清楚为什么 Task.Run (在不同的线程上执行)会陷入(临时)死锁,即使 DelayedConstraint
已允许主线程继续进行的显式轮询间隔。
看起来DelayedConstraint
通过某种Thread.Sleep
锁定了主线程。 await Task.Delay
不会,我知道这一点。让我困惑的是我已经检查过我总是执行 await
(并且从不 Task.Result
等),因此期望在断言之前已写入文件已执行。
(注意:用 Thread.Sleep
代替 await Task.Delay
不起作用。)
通常,DelayedConstraint
用于确保文件系统已正确写入所有文件,因为我在文件系统处理文件时遇到了一些延迟。
我有一些感觉,async void
事件处理程序可能会造成我不理解的情况。
如果我设法创建一个简单的示例,我将更新线程。
最佳答案
与 VS2012 单元测试类比,尝试使用 async Task
签名而不是 async void
作为您的测试方法。这样,NUnit 应该能够跟踪挂起的任务状态并通过 Task.Exception 检查异常。
根据定义,async void
方法是一个即发即忘的概念。该方法立即返回(准确地说,是在其中第一个异步 await
时),然后无法处理其完成或其中可能抛出的任何错误。按原样,async void
方法 are only good for event handlers ,前提是在方法内使用 try/catch
处理异常。
关于c# - nUnit Assert.That委托(delegate)并发问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22205708/