【发布时间】:2014-03-07 12:38:16
【问题描述】:
我的代码遇到了一些暂时的死锁,我无法理解它。
简单代码(我无法创建简单的调用链来重现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 的委托,它返回 DelayedConstraint,并且具有非常大的最大时间(15 秒)和小的轮询间隔。
现在,当我调试代码时,InvokeChangeEvent 调用完全执行到最后一个 Task.Run,但是当 Task.Run 返回时,执行返回到主线程并执行 Assert,进入“轮询等待” .
但是断言永远不会成功。当我调试问题时,Task.Run 的返回总是在 Assert 委托运行(并且失败)之后处理。
我发现,当我在 Assert 之前放置 await Task.Delay(3000); 时,代码会正确执行。
如前所述,被测系统有很多 await 和 Task.Runs 链接,我无法使用一些简单的可运行代码重现该问题。
我已经在谷歌上搜索了一段时间,但我无法弄清楚为什么 Task.Run(在不同的线程上执行)会导致(临时)死锁,即使 DelayedConstraint 有一个明确的轮询间隔让主线程继续。
看起来DelayedConstraint 通过某种Thread.Sleep 锁定了主线程。 await Task.Delay 没有,我知道这一点。让我感到困惑的是,我已经检查过我是否总是执行 await(而不是 Task.Result 等),因此预计该文件是在 Assert 执行之前写入的。
(注意:Thread.Sleep 代替 await Task.Delay 不起作用。)
通常DelayedConstraint 用于确保文件系统已正确写入所有文件,因为我遇到了文件系统处理文件的一些延迟。
我感觉async void 事件处理程序可能会造成我不理解的情况。
如果我设法创建一个简单的示例,我将更新线程。
【问题讨论】:
-
你试过
public async Task Test()而不是public async void Test()吗? -
@Noseratio 感谢您的提示,我会尽快对其进行测试。我不知道 nUnit 支持异步任务。我很幸运,他们扩展了测试以完全支持异步。但是,由于我这里没有代码,我想知道这是否会编译,因为我没有在上面的代码中返回任务。
-
Samuel,实际上我不确定 NUnit 是否完全支持异步方法,但
Task与void的机会更高。 VS2012+ 单元测试就是这种情况。如果 NUnit 不支持它,也许您仍然可以返回Task并在 NUnit 包装器中执行Task.Wait()。 -
@Noseratio 确实是异步无效。由于 NUnit 似乎支持异步任务,我已将所有异步 void 转换为它以避免这种停滞。如果您对您的评论做出答复,我会将其标记为答复。
标签: c# multithreading asynchronous nunit task-parallel-library