Альтернатива `wxProcess :: IsInputAvailable` - PullRequest
0 голосов
/ 05 августа 2020

Согласно документации , метод wxProcess::IsInputAvailable «позволяет писать простой (и крайне неэффективный) код, основанный на опросах, ожидая лучшего механизма в будущих версиях wxWidgets». Это также указывает на пример кода, датированный «15.01.00», я проверил на GitHub , что использование этого метода датируется 16 или более годами.

Хорошо, этот метод объединения все еще неэффективен? Есть ли лучшая альтернатива?

Я отлаживаю проблему с выходом процесса, который не читается, используя что-то похожее на образец exe c, и в любом случае мое приложение чувствительно к своевременность этого опроса и извлечения вывода.

1 Ответ

1 голос
/ 06 августа 2020

Я не думаю, что методы чтения перенаправленного вывода или дочернего процесса неэффективны. Я думаю, что в документе утверждается, что опрос в потоке GUI, как в примере, будет неэффективным, так как он включает в себя многократное создание событий псевдо-ожидания.

Возможной альтернативой является использование вторичного потока для чтения. Вот пример использования хромого mp3-кодировщика для кодирования wav-файла:

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include <wx/filename.h>
#include <wx/filepicker.h>
#include <wx/msgqueue.h>
#include <wx/thread.h>
#include <wx/process.h>

const char* pathToLame = "d:\\temp\\lame.exe";

wxDEFINE_EVENT(wxEVT_THREAD_STDIN, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_THREAD_STDERR, wxThreadEvent);

class LameThread : public wxThread
{
    public:
        enum ThreadMessage
        {
            ProcessComplete,
            ExitThread,
            MessageLast
        };

        LameThread(wxEvtHandler*, wxProcess*, wxMessageQueue<ThreadMessage>& q);
        ~LameThread();

    private:
        ExitCode Entry() wxOVERRIDE;
        void DrainInput();

        wxMessageQueue<ThreadMessage>& m_queue;
        wxEvtHandler* m_handler;
        wxProcess* m_process;
        char* m_buffer;
        size_t m_bufferSize;
};

LameThread::LameThread(wxEvtHandler* h, wxProcess* p,
                       wxMessageQueue<ThreadMessage>& q)
           :wxThread(wxTHREAD_JOINABLE),m_queue(q)
{
    m_process = p;
    m_handler = h;
    m_bufferSize = 1024*1024;
    m_buffer = new char[m_bufferSize];
}

LameThread::~LameThread()
{
    delete[] m_buffer;
    delete m_process;
}

wxThread::ExitCode LameThread::Entry()
{
    ExitCode c;

    while ( 1 )
    {
        // Check if termination was requested.
        if ( TestDestroy() )
        {
            wxProcess::Kill(m_process->GetPid());
            c = reinterpret_cast<ExitCode>(1);
            break;
        }

        ThreadMessage m = MessageLast;
        wxMessageQueueError e = m_queue.ReceiveTimeout(10, m);

        // Check if a message was received or we timed out.
        if ( e == wxMSGQUEUE_NO_ERROR )
        {
            if ( m == ProcessComplete )
            {
                DrainInput();
                c = reinterpret_cast<ExitCode>(0);
                break;

            }
            else if ( m == ExitThread )
            {
                wxProcess::Kill(m_process->GetPid());
                c = reinterpret_cast<ExitCode>(1);
                break;
            }
        }
        else if ( e == wxMSGQUEUE_TIMEOUT )
        {
            DrainInput();
        }
    }

    return c;
}

void LameThread::DrainInput()
{
    if ( !m_process->IsInputOpened() )
    {
        return;
    }

    wxString fromInputStream, fromErrorStream;
    wxInputStream* stream;

    while ( m_process->IsInputAvailable() )
    {
        stream = m_process->GetInputStream();
        stream->Read(m_buffer, m_bufferSize);
        fromInputStream << wxString(m_buffer, stream->LastRead());
    }

    while ( m_process->IsErrorAvailable() )
    {
        stream = m_process->GetErrorStream();
        stream->Read(m_buffer, m_bufferSize);
        fromErrorStream << wxString(m_buffer, stream->LastRead());
    }

    if ( !fromInputStream.IsEmpty() )
    {
        wxThreadEvent* event = new wxThreadEvent(wxEVT_THREAD_STDIN);
        event->SetString(fromInputStream);
        m_handler->QueueEvent(event);
    }

    if ( !fromErrorStream.IsEmpty() )
    {
        wxThreadEvent* event = new wxThreadEvent(wxEVT_THREAD_STDERR);
        event->SetString(fromErrorStream);
        m_handler->QueueEvent(event);
    }
}

class MyFrame: public wxFrame
{
    public:
        MyFrame();

    private:
        void OnClose(wxCloseEvent& event);
        void OnEncode(wxCommandEvent& event);
        void OnProcessComplete(wxProcessEvent& event);
        void OnThreadInput(wxThreadEvent&);

        wxThread* m_lameThread;
        wxMessageQueue<LameThread::ThreadMessage> m_msgQueue;
        wxFilePickerCtrl* m_filePicker;
        wxTextCtrl* m_textCtrl;
        wxButton* m_encodeButton;
};

MyFrame::MyFrame()
        :wxFrame(NULL, wxID_ANY, "Encode", wxDefaultPosition, wxSize(600, 400))
{
    m_lameThread = NULL;

    wxPanel* panel = new wxPanel(this, wxID_ANY);
    m_filePicker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString,
                                     "Select a file", "*.wav");
    m_textCtrl = new wxTextCtrl(panel, wxID_ANY, wxEmptyString,
                                wxDefaultPosition, wxDefaultSize,
                                wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY);
    m_encodeButton = new wxButton( panel, wxID_ANY, "Encode");

    wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);

    sizer->Add(m_filePicker, wxSizerFlags().Expand().Border(wxALL) );
    sizer->Add(m_textCtrl,
               wxSizerFlags(1).Expand().Border(wxLEFT|wxRIGHT|wxBOTTOM));
    sizer->Add(m_encodeButton,
               wxSizerFlags().Border(wxLEFT|wxRIGHT|wxBOTTOM).Right());

    panel->SetSizer( sizer );
    panel->Layout();

    m_encodeButton->Bind(wxEVT_BUTTON, &MyFrame::OnEncode, this);
    Bind(wxEVT_CLOSE_WINDOW, &MyFrame::OnClose, this);
    Bind(wxEVT_END_PROCESS, &MyFrame::OnProcessComplete, this);
    Bind(wxEVT_THREAD_STDIN, &MyFrame::OnThreadInput, this);
    Bind(wxEVT_THREAD_STDERR, &MyFrame::OnThreadInput, this);
}

void MyFrame::OnClose(wxCloseEvent& event)
{
    if ( m_lameThread && m_lameThread->IsRunning() )
    {
        m_msgQueue.Post(LameThread::ExitThread);
        m_lameThread->Wait();
        delete m_lameThread;
    }

    Destroy();
}

void MyFrame::OnEncode(wxCommandEvent& event)
{
    // Make sure the input file exists and is a wav file.
    wxString file = m_filePicker->GetPath();

    if ( !wxFileName::FileExists(file) )
    {
        m_textCtrl->AppendText("file does not exist.\n");
        return;
    }

    wxFileName fn(file);

    if ( fn.GetExt() != "wav" )
    {
        m_textCtrl->AppendText("File is not a wav file.\n");
        return;
    }

    // Create a process and and encoder thread.
    wxProcess* process = new wxProcess(this);
    process->Redirect();

    m_msgQueue.Clear();
    m_lameThread = new LameThread(this, process, m_msgQueue);
    m_lameThread->Run();

    if ( !m_lameThread->IsRunning() )
    {
        m_textCtrl->AppendText("Unable to launch encoder thread.\n");
        delete m_lameThread;
        return;
    }

    // Execute the encoder command.
    wxString cmd = pathToLame;
    cmd << " \"" << fn.GetFullPath() << "\" \"";
    cmd << fn.GetPathWithSep() << fn.GetName() << ".mp3\"";

    wxExecute(cmd, wxEXEC_ASYNC, process);
    m_encodeButton->Disable();
}

void MyFrame::OnProcessComplete(wxProcessEvent& event)
{
    if ( m_lameThread && m_lameThread->IsRunning() )
    {
        m_msgQueue.Post(LameThread::ProcessComplete);
        m_lameThread->Wait();
        delete m_lameThread;
        m_lameThread = NULL;
        m_encodeButton->Enable();
    }
}

void MyFrame::OnThreadInput(wxThreadEvent& event)
{
    m_textCtrl->AppendText(event.GetString());
}

class MyApp : public wxApp
{
    public:
        virtual bool OnInit()
        {
            ::wxInitAllImageHandlers();
            MyFrame* frame = new MyFrame();
            frame->Show();
            return true;
        }
};

wxIMPLEMENT_APP(MyApp);

В windows это выглядит так:

введите описание изображения здесь

Основная идея c состоит в том, чтобы запустить плотный l oop в методе потока Entry. На каждой итерации поток ожидает сообщения из очереди сообщений, и, если сообщение не получено, поток пытается проверить процесс на предмет ввода или сообщений об ошибках.

По сути, это тот же метод опроса, что и в примере , но поскольку он находится во вторичном потоке, он не забивает поток gui событиями бездействия. Если вам нужно выполнить какую-либо обработку ввода, это также будет выполнено во вторичном потоке.

Если у вас возникли проблемы с таймингом, вы можете сократить время ожидания в очереди сообщений.

Обратите внимание, что запуск вторичного pro css и получение события после его завершения может быть выполнено только в основном потоке.

Наконец, я должен упомянуть, что я не уверен на 100%, что это полностью потокобезопасный. У sh был способ защитить чтение и запись с входными потоками и потоками ошибок с помощью мьютекса или критического раздела; но поскольку вся запись в эти потоки выполняется «под капотом», я не вижу способа сделать это из пользовательского кода. Могу сказать, что в прошлом я без проблем использовал эту технику как на windows, так и на linux.

...