C ++异步OutputDebugString

时间:2013-01-26 11:17:43

标签: c++ asynchronous c++11

使用c ++ 11中的新std :: async,我想我可能会尝试实现一个异步版本的OutputDebugString来解除我通常大量打印每个细节所带来的性能下降通过通常的OutputDebugString函数。

所以这是我原来的同步OutputDebugString实现(可以工作):

static void OutputDebugStringN(const char *format, ...)
{
    char outstring[256];
    memset(outstring, 0, sizeof(outstring));

    try
    {
        va_list args = {0};
        va_start(args, format); //args = (va_list) (&format+1);

        vsprintf_s(outstring, format, args);

        va_end(args);

        OutputDebugString(outstring);
    }
    catch (...) //most likely reference val arg error (va_list doesn't support ref args)
    {
        OutputDebugString("[OutputDebugStringN] Something went wrong\n");
    }
}

以及我对非同步版本的非常有效的尝试(不起作用):

static void OutputDebugStringN(const char *format, ...)
{
    auto future = std::async([]{
        char outstring[256];
        memset(outstring, 0, sizeof(outstring));
        try
        {
            va_list args = {0};
            va_start(args, format); //args = (va_list) (&format+1);

            vsprintf_s(outstring, format, args);

            va_end(args);

            OutputDebugString(outstring);
        }
        catch (...) //most likely reference val arg error (va_list doesn't support ref args)
        {
            OutputDebugString("[OutputDebugStringN] Something went wrong\n");
        }
    }); 
}

由于以上方法不起作用,我现在正处于开始认为异步调用OutputDebugStringN可能比尝试在函数内部启动异步作业更好的方面(这有效,但是繁琐的):

auto dstring = std::async([]{ OutputDebugStringN("[NovelScript::ParseTokens] searched bookmark: \"%s\" does not exist\n", bookmark.c_str());} );

所以这里有两个我想问的问题:

  1. 我应该如何实现OutputDebugString的异步版本?
  2. 我是否应该尝试实现OutputDebugString的异步版本?
  3. 对上述代码和任何其他评论的批评也非常受欢迎。

3 个答案:

答案 0 :(得分:7)

我认为你应该为你的消息设置一个队列,而不是每次调用你的函数都启动一个线程,这样你的消息就会以正确的顺序输出。

所以你的功能,例如OutputDebugStringN(const char *format, ... )将创建消息字符串,然后对单独的打印输出线程读取的字符串进行排队。那个帖子会调用OutputDebugString

这是一个例子 - 虽然不完整,但是不应该修改错误处理和print_from_queue直到某些终止条件运行并且对CPU更友好。

std::mutex g_m;
std::deque<std::string> que;
std::atomic<bool> endcond = false;

void queue(std::string msg)
{
  std::lock_guard<mutex> _(g_m);
  que.push_back(msg);
}

void print_from_queue()
{
  while ( !endcond )
  {
    if ( que.size() )
    {
      std::lock_guard<mutex> _(g_m);
      std::string msg = que.front();
      que.pop_front();
      OutputDebugStringA(msg.c_str());
    }
  }
}

int debugf( const char *format,... )
{
  std::vector<char> line(256);
  va_list args;
  va_start( args, format );
  int len = vsprintf_s( &line[0], line.size(), format, args );
  va_end( args );
  queue( &line[0] );
  return len;
}

int _tmain(int argc, _TCHAR* argv[])
{
  auto thr = std::async( std::launch::async, print_from_queue );
  debugf("message1");
  debugf("message2");
...

答案 1 :(得分:3)

在我看来,调试应该是同步的,而不是异步的。如果调试器在发生后提供异常秒数,您会感激吗?在您的程序崩溃后(因为您创建了文件写入asnyc),您是否会欣赏具有程序状态陈旧数据的日志文件?

无论如何,你继续将调试输出变为异步。它解决了什么目的,除了将数据转储到调试窗口,你知道它不是最近的,它不反映你的/用户或程序的动作,它都是陈旧的!你完全不能依赖它。

答案 2 :(得分:2)

批评上面的代码和C ++异步函数:: p

std :: async的返回值是std :: future类型的对象。由std :: async创建的std :: future的析构函数等待,直到任务执行完毕。所以当你这样做时:

auto future = std::async(...

auto dstring = std::async([]{

它创建了一个std :: future类型的对象,当你离开OutputDebugStringN的范围时,它会调用std :: future的析构函数,它会阻塞,直到任务执行完毕。

在我看来,这是C ++的一个缺陷。它有些愚蠢(希望这不会冒犯任何人:p),它完全违背了异步的目的。为了获得大多数人期望的行为(显然,你期望它),你必须保留std :: future对象的列表,然后花费精力(和处理时间)找出销毁个别std的正确时间: :列表中的未来对象。这是OP中问题#1的答案。对于#2,我认为你不应该在每个调试消息的基础上为此目的使用std :: async。我认为它产生的问题多于解决的问题。

我不知道这有一种优雅的方式。也许别人可以插话。

至于我如何实现OutputDebugString的异步版本,我只想创建一个生产者 - 消费者队列的字符串。有很多问题,你可以谷歌生产者 - 消费者队列的细节。生产者是发出消息的主要线程。使用者是一个线程(或几个线程),从队列中挑出元素并调用Window的OutputDebugString。

编辑:如果我冒犯了任何异步爱好者,我想补充一点,std :: async对于进行并行计算非常有用,就像使用GPU一样。我怀疑它是专为利用并行硬件而设计的。例如:

      // merge sort
      {
           auto left =  std::async(.....);  // merge_sort left
           auto right =  std::async(.....);  // merge_sort right
      }

      merge
以这种方式,在我们合并之前,必须对左边和右边进行排序。如果没有,请等到它们都被排序,但它允许有机会同时处理左右两个。

如果您已完成CUDA或任何GPGPU编码,这应该看起来非常熟悉......