我的代码中遇到了一些暂时的死锁,无法解决它。
简单的代码(我无法创建一个简单的调用链来重现代码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个文件。
上面的断言是作为委托实现的After
返回一个DelayedConstraint
并且有一个非常大的最大时间(15秒)和一个小的轮询间隔。
现在,当我调试代码时,InvokeChangeEvent 调用完全执行到最后一个 Task.Run,但是当 Task.Run 返回时,执行将返回到主线程,并且执行断言,进入“等待轮询”。
然而断言永远不会成功。当我调试问题时,总是处理 Task.Run 的返回after断言委托已运行(并且失败)。
我发现,当我放置一个await Task.Delay(3000);
在断言之前,那么代码就可以正确执行。
如前所述,正在测试的系统有大量的等待和任务。运行链接,我无法使用一些简单的可运行代码重现该问题。
我已经搜索了一段时间,我无法弄清楚为什么 Task.Run (在不同的线程上执行)会产生(临时)死锁,即使DelayedConstraint
有一个明确的轮询间隔以允许主线程继续进行。
它看起来像DelayedConstraint
通过某种方式锁定主线程Thread.Sleep
. await Task.Delay
不,我知道这一点。让我困惑的是我已经检查过我总是做await
(永不Task.Result
等),因此期望在执行断言之前已写入该文件。
(Note: Thread.Sleep
代替await Task.Delay
不起作用。)
通常是DelayedConstraint
用于确保文件系统已正确写入所有文件,因为我经历过文件系统处理文件的一些延迟。
我有一些感觉async void
事件处理程序可能会造成我不理解的情况。
如果我设法创建一个简单的示例,我将更新该线程。