我的代码中遇到了一些临时死锁,无法绕过它。
简单代码(我无法创建一个简单的调用链来重现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));
}
我知道y'所有(:D)都需要可执行代码,但我无法将其分解,因此我希望通过解释获得一些见解。
会发生什么:sut.InvokeChangeEvent
调用事件处理程序,该处理程序稍后调用async
事件处理程序,然后调用某些async
。链的末尾产生Task.Run
,归结为写3个文件。
以上Assert实现为After
的委托,返回DelayedConstraint
并且具有非常大的最长时间(15秒)和小的轮询间隔。
现在,当我调试代码时,InvokeChangeEvent调用完全执行到最后一个Task.Run但是当Task.Run返回时,执行被返回到主线程并且执行Assert进入"等待投票"。
然而,断言永远不会成功。当我调试问题时,在Assert委托运行(并且失败)后,始终会返回Task.Run 。
我已经想通了,当我在Assert之前放置一个await Task.Delay(3000);
时,代码就会正确执行。
如上所述,受测试的系统有足够的等待和Task.Runs链接,我无法用一些简单的可运行代码重现该问题。
我已经谷歌搜索了一段时间,我无法弄清楚为什么Task.Run(在不同的线程上执行)会产生(临时)死锁,即使DelayedConstraint
有一个显式轮询间隔,以允许主线程进展。
看起来DelayedConstraint
通过某种Thread.Sleep
锁定主线程。 await Task.Delay
没有,我知道这一点。让我感到困惑的是,我已经检查过我总是做await
(而且从不Task.Result
等),因此希望文件在Assert执行之前编写。
(注意:Thread.Sleep
代替await Task.Delay
不起作用。)
通常DelayedConstraint
用于确保文件系统正确编写了所有文件,因为我遇到了处理文件的文件系统的一些延迟。
我有一种感觉async void
事件处理程序可能会造成我不理解的情况。
如果我设法创建一个简单的示例,我将更新该线程。
答案 0 :(得分:3)
与VS2012单元测试类比,请尝试使用async Task
签名而不是async void
作为测试方法。这样,NUnit应该能够跟踪待处理的任务状态并通过Task.Exception
检查异常。
根据定义,async void
方法是一种即发即弃的概念。该方法立即返回(确切地说,在它内部的第一个异步await
),然后没有办法处理它的完成或可能在其中抛出的任何错误。通常,async void
方法are only good for event handlers提供了try/catch
方法中的异常处理。