1

我正在使用带有回调函数的waveOutWrite,并且在本机代码下一切都很快。在 .NET 下它要慢得多,以至于我认为我做错了什么,有时慢 5 到 10 倍。

我可以发布两组代码,但似乎太多了,所以我只发布速度很快的 C 代码并指出 .NET 代码中的微小差异。

HANDLE WaveEvent;
const int TestCount = 100;
HWAVEOUT hWaveOut[1]; // don't ask why this is an array, just test code
WAVEHDR woh[1][20];

void CALLBACK OnWaveOut(HWAVEOUT,UINT uMsg,DWORD,DWORD,DWORD)
{
   if(uMsg != WOM_DONE)
      return;
   assert(SetEvent(WaveEvent)); // .NET code uses EventWaitHandle.Set()
}

void test(void)
{
   WaveEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
   assert(WaveEvent);

   WAVEFORMATEX wf;
   memset(&wf,0,sizeof(wf));
   wf.wFormatTag =  WAVE_FORMAT_PCM;
   wf.nChannels = 1;
   wf.nSamplesPerSec = 8000;
   wf.wBitsPerSample = 16;
   wf.nBlockAlign = WORD(wf.nChannels*(wf.wBitsPerSample/8));
   wf.nAvgBytesPerSec = (wf.wBitsPerSample/8)*wf.nSamplesPerSec;

   assert(waveOutOpen(&hWaveOut[0],WAVE_MAPPER,&wf,(DWORD)OnWaveOut,0,CALLBACK_FUNCTION) == MMSYSERR_NOERROR);

   for(int x=0;x<2;x++)
      {
      memset(&woh[0][x],0,sizeof(woh[0][x]));
      woh[0][x].dwBufferLength = PCM_BUF_LEN;
      woh[0][x].lpData = (char*) malloc(woh[0][x].dwBufferLength);
      assert(waveOutPrepareHeader(hWaveOut[0],&woh[0][x],sizeof(woh[0][x])) == MMSYSERR_NOERROR);
      assert(waveOutWrite(hWaveOut[0],&woh[0][x],sizeof(woh[0][x])) == MMSYSERR_NOERROR);
      }

   int bufferIndex = 0;
   DWORD times[TestCount];
   for(int x=0;x<TestCount;x++)
      {
      DWORD t = timeGetTime();
      assert(WaitForSingleObject(WaveEvent,INFINITE) == WAIT_OBJECT_0); // .NET code uses EventWaitHandle.WaitOne()
      assert(woh[0][bufferIndex].dwFlags & WHDR_DONE);
      assert(waveOutWrite(hWaveOut[0],&woh[0][bufferIndex],sizeof(woh[0][bufferIndex])) == MMSYSERR_NOERROR);
      bufferIndex = bufferIndex == 0 ? 1 : 0;
      times[x] = timeGetTime() - t;
      }
}

C 代码的 times[] 数组的值总是在 80 左右,这是我正在使用的 PCM 缓冲区长度。.NET 代码有时也会显示类似的值,但有时会显示高达 1000 的值,更常见的是 300 到 500 范围内的值。

在 OnWaveOut 回调中执行底部循环中的部分而不是使用事件,使用 .NET 或本机代码使其始终保持快速。因此,问题似乎仅与 .NET 中的等待事件有关,并且主要仅在测试 PC 上发生“其他事情”时——但不是很多事情,可以像移动窗口或打开一样简单我电脑里的一个文件夹。

也许 .NET 事件对于上下文切换或 .NET 应用程序/线程一般来说真的很糟糕?在我用来测试我的 .NET 代码的应用程序中,代码只是在表单的构造函数中运行(添加测试代码的容易的地方),而不是在线程池线程或任何东西上。

我还尝试使用接收事件而不是函数回调的 waveOutOpen 版本。这在 .NET 中也很慢,但在 C 中则不然,因此,它再次指出了事件和/或上下文切换的问题。

我试图让我的代码保持简单,并设置一个事件来完成回调之外的工作是我可以用我的整体设计做到这一点的最佳方式。实际上只使用事件驱动的 waveOut 更好,但我尝试了另一种方法,因为直接回调很快,而且我没想到正常的事件等待句柄会这么慢。

4

1 回答 1

0

也许不是 100% 相关,但我遇到了同样的问题:调用EventWaitHandle.SetX 次很好,但是,在我无法提及的阈值之后,这个方法的每次调用都需要 1 秒!

似乎某些 .net 同步线程的方法比您在 C++ 中使用的方法慢得多。

强大的@jonskeet 曾经在他的网站 ( https://jonskeet.uk/csharp/threads/waithandles.html ) 上发了一个帖子,他还提到了此处解释的 .net 同步域的非常复杂的概念:https:// www.drdobbs.com/windows/synchronization-domains/184405771

他提到.net 和操作系统必须以非常非常非常精确的方式与必须从一个环境转换到另一个环境的对象进行通信。所有这些都非常耗时。

我在这里总结了很多,不是为了得到答案,而是有一个解释。这里有一些建议(https://docs.microsoft.com/en-us/dotnet/standard/threading/overview-of-synchronization-primitives)关于根据上下文和性能选择同步方式的一些方法方面稍微提到了一点。

于 2021-03-18T11:21:16.563 回答