为什么此 ThreadPool.QueueUserWorkItem 代码没有通过我的测试?
有问题的代码
public void StartPlaying()
{
ThreadPool.QueueUserWorkItem(ignoredState =>
{
while (_playlist.Count > 0)
{
var audioFile = _playlist.Dequeue();
if (StartedPlaying != null)
StartedPlaying(this, new TypedAudioFileEventArgs(audioFile));
audioFile.SoundPlayer.PlaySync();
audioFile.SoundPlayer.Dispose();
if (StoppedPlaying != null)
StoppedPlaying(this, new TypedAudioFileEventArgs(audioFile));
}
});
}
和我的测试:
[TestMethod()]
public void StartPlayIsCalledTwice_OnlyRunningOnce()
{
int timeBetweenPlays = 0;
var target = new TypedAudioFilePlayer(timeBetweenPlays);
target.AddFile(TypedAudioFileCreator.CreateWord(1, "bl"));
target.StartedPlaying += StartedPlaying_Once;
target.StartPlaying();
target.StartPlaying();
}
private bool _once = false;
private void StartedPlaying_Once(object sender, TypedAudioFileEventArgs e)
{
if (!_once)
_once = true;
else
Assert.Fail("Should not be called more than once!");
}
根据 ThreadPool.QueueUserWorkItem
的 MSDN 描述判断,我相信我的单元测试应该失败:
对方法进行排队以供执行。当线程池线程变得可用时,该方法将执行。
默认线程池大小为 512,因此应立即有两个线程可用于处理 StartPlaying 调用。我相信我的代码应该失败,因为我没有提供任何针对两个线程可以访问相同资源的竞争条件的保护措施。
这里发生了什么事?
The code in question
public void StartPlaying()
{
ThreadPool.QueueUserWorkItem(ignoredState =>
{
while (_playlist.Count > 0)
{
var audioFile = _playlist.Dequeue();
if (StartedPlaying != null)
StartedPlaying(this, new TypedAudioFileEventArgs(audioFile));
audioFile.SoundPlayer.PlaySync();
audioFile.SoundPlayer.Dispose();
if (StoppedPlaying != null)
StoppedPlaying(this, new TypedAudioFileEventArgs(audioFile));
}
});
}
and my test:
[TestMethod()]
public void StartPlayIsCalledTwice_OnlyRunningOnce()
{
int timeBetweenPlays = 0;
var target = new TypedAudioFilePlayer(timeBetweenPlays);
target.AddFile(TypedAudioFileCreator.CreateWord(1, "bl"));
target.StartedPlaying += StartedPlaying_Once;
target.StartPlaying();
target.StartPlaying();
}
private bool _once = false;
private void StartedPlaying_Once(object sender, TypedAudioFileEventArgs e)
{
if (!_once)
_once = true;
else
Assert.Fail("Should not be called more than once!");
}
I believe my unit test should fail, judging by the MSDN description of ThreadPool.QueueUserWorkItem
:
Queues a method for execution. The method executes when a thread pool thread becomes available.
The default ThreadPool size is 512, so two threads should immediately be available to process the StartPlaying call. I believe my code should fail since I haven't provided any safeguards from race conditions in which both threads can access the same resource.
What's happening here?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
因为仅当有项目要播放时调用 StartPlaying 时才会引发
StartedPlaying
事件。_playlist.Dequeue();
将您入队的文件出队。因此,当您第二次到达while (_playlist.Count > 0)
时,它将立即失败,直接将第二次调用传递给StartPlaying
而不会引发事件。此外,正如 Bruno Silva 指出的那样,第二次调用
StartPlaying
生成的线程在测试退出之前可能没有机会执行任何操作。无论如何,此代码中还存在
大约一百万至少 2 个线程错误:如果要正确进行单元测试,则需要定义前置条件、期望、操作和后置条件:
先决条件:您有一个已初始化的 TypedAudioFilePlayer,其中有一个文件已排队:
var target = new TypedAudioFilePlayer(timeBetweenPlays);
target.AddFile(TypedAudioFileCreator.CreateWord(1, "bl"));
期望:如果 StartPlaying 被调用两次,StartedPlaying 事件将仅引发一次
target.StartedPlaying += StartedPlaying_Once;
操作: StartPlaying 方法将被调用两次:
target.StartPlaying();
target.StartPlaying();
后置条件:
StartedPlaying
事件仅引发一次:private bool _once = false;
private void StartedPlaying_Once(对象发送者,TypedAudioFileEventArgs e)
<代码>{
如果(!_once)
_once = true;
其他
Assert.Fail("不应多次调用!");
}
现在,您的测试成功了。由于我上面的解释,在这种情况下这并不好。您需要通过消除队列错误和竞争条件来使测试进入失败状态,然后努力使测试以正确的方式通过。
Because the
StartedPlaying
event is only raised if StartPlaying is called when there are items to play._playlist.Dequeue();
dequeues the file you enqueue. Therefore the second time you get towhile (_playlist.Count > 0)
it will immediately fail, passing the second call toStartPlaying
straight through without raising the event.Also, as Bruno Silva points out, the thread spawned by the second call to
StartPlaying
may not have a chance to execute anything before the test exits.For what it's worth, there are
about a millionat least 2 threading mistakes in this code also:If you want to properly unit test, you need to define preconditions, expectations, actions, and postconditions:
Preconditions: you have an initialized TypedAudioFilePlayer with one file queued:
var target = new TypedAudioFilePlayer(timeBetweenPlays);
target.AddFile(TypedAudioFileCreator.CreateWord(1, "bl"));
Expectations: The StartedPlaying event will be raised only once if StartPlaying is called twice
target.StartedPlaying += StartedPlaying_Once;
Actions: The StartPlaying method will be called twice:
target.StartPlaying();
target.StartPlaying();
Postconditions: The
StartedPlaying
event was only raised once:private bool _once = false;
private void StartedPlaying_Once(object sender, TypedAudioFileEventArgs e)
{
if (!_once)
_once = true;
else
Assert.Fail("Should not be called more than once!");
}
Now, your test succeeds. That's not good in this case, because of what I explain above. You need to get your test to a failing state by eliminating the queue bug and race condition, then work on making the test pass the right way.
您似乎正在使用两个线程之间的共享资源,因此当第二次调用 Play 时,once 可能不会设置为 true。您可以使用锁来允许一次由一个线程执行部分代码:
It seems that you are working with a shared resource between two threads so once might not be set to true when the Play is called for the second time. You can use a lock to allow executing part of your code by one thread at a time :
这些可能会在文本执行之外失败吗?您的测试在您对项目进行排队后立即结束,因此我不确定当测试方法结束执行时这些线程会发生什么。您是否尝试过使用
WaitHandle
等待它们在测试中完成?请参阅 http://msdn.microsoft.com/en-us/ Library/system.threading.waithandle.aspx 为例。
Could those be failing outside the text execution? Your test ends right after you queue the item so I'm not sure what is happening to those threads when the test method ends its execution. Have you tried using
WaitHandle
to wait for the them to finish inside the test?See http://msdn.microsoft.com/en-us/library/system.threading.waithandle.aspx for an example.