簡體   English   中英

如何與子進程異步通信?

[英]How to communicate with child process asynchronously?

我有一個用 GTKmm 構建的父 GUI 應用程序,我需要生成一個子進程(另一個 GUI 應用程序)並與之通信。 我使用 boost::process 來做到這一點。 我知道我應該異步執行,這樣父 UI 就不會被阻塞。

所以問題:

  • 如何從子應用程序異步收聽任何 output 並進行處理?
  • 我怎么知道子應用程序/進程何時關閉/終止?

這是我目前的做法(阻塞了用戶界面):

#include <iostream>
#include <boost/process.hpp>
#include <gtkmm.h>

using namespace std;
using namespace boost::process;

class MyWindow : public Gtk::Window
{
public:
MyWindow();

private:
Gtk::Button *start_btn;

void Start();
};

void MyWindow::Start() {
// The target app is built from .NET 5.0 to run on RPi (linux-arm)

ipstream pipe_stream;
// change to your own target process
child c("/usr/bin/dotnet", "/home/pi/updater/Updater.dll", std_out > pipe_stream);
std::string line;
bool upToDate;
while (pipe_stream && std::getline(pipe_stream, line) && !line.empty()) {
  std::cout << line << std::endl;
  try {
    upToDate = line == "True" || line == "true" || line == "1";
    if (upToDate) {
      std::cout << "up-to-date" << std::endl;
      break;
    }
    else {
      std::cout << "update available!" << std::endl;
      break;
    }
  }
  catch(exception& e) {
    std::cerr << e.what() << std::endl;
  }

}


c.wait();
}

MyWindow::MyWindow()
{
set_title("Basic application");
set_default_size(200, 200);
start_btn = Gtk::make_managed<Gtk::Button>("Start process");

start_btn->signal_clicked().connect(sigc::mem_fun(*this, &MyWindow::Start));

this->add(*start_btn);
this->show_all();
}

int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.examples.base");

MyWindow win;

return app->run(win);
}

此代碼使用 GTKmm 3.0 lib

如您所料, Start()方法會阻塞,因此其他 Gtk 代碼沒有機會運行。 這意味着什么都沒有完成,甚至沒有繪制 UI。

相反,讓child成為 class 的成員。接下來,使用async_pipe而不是阻塞 pipe stream,這樣你就不必阻塞來讀取。 現在,設置異步讀取循環以響應來自子進程標准 output 的傳入數據。

我創建了一個簡單的 do.net 核心控制台應用程序來測試它:

mkdir CORE && cd CORE
dotnet build
dotnet bin/Debug/net6.0/CORE.dll 

現在我們將默認的 Program.cs 替換為:

for (int i = 1; i<11; ++i)
{
    Console.WriteLine("Hello, World {0}!", i);
    System.Threading.Thread.Sleep(500);
}
Console.WriteLine("Bye, World!");
return 42;

構建並再次運行打印,總時間跨度為 5 秒:

Hello, World 1!
Hello, World 2!
Hello, World 3!
Hello, World 4!
Hello, World 5!
Hello, World 6!
Hello, World 7!
Hello, World 8!
Hello, World 9!
Hello, World 10!
Bye, World!

做 GTK 邊

我簡化了很多事情。

最棘手的部分是讓io_context從 Gtk 事件循環中被輪詢。 為此,我選擇使用g_add_timeout 正確取消注冊滴答處理程序非常重要,因此在MyWindow被破壞后不會產生未定義的行為。

tick()每 10 毫秒運行一次(如果可能)。 也許對於您的用例,您可以降低頻率。

我添加了一個Stop按鈕作為良好的措施,並確保Start / Stop按鈕已根據需要啟用/禁用。 讓我們做一些現場演示:

完整演示

#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <gtkmm.h>
#include <iostream>
namespace asio = boost::asio;
namespace bp   = boost::process;

class MyWindow : public Gtk::Window {
  public:
    MyWindow();
    ~MyWindow() override;

  private:
    Gtk::Box    box_{Gtk::Orientation::ORIENTATION_VERTICAL, 4};
    Gtk::Button btnStart_{"Start Updater"};
    Gtk::Button btnStop_{"Stop Updater"};
    Gtk::Label  lblOutput_{"(click the start button)"};

    void StartUpdater();
    void StopUpdater();

    guint tick_source_{0};

    using Ctx = asio::io_context;
    Ctx                        io_;
    boost::optional<Ctx::work> work_{io_};

    struct AsyncUpdater {
        AsyncUpdater(MyWindow& win) : win_(win) { read_loop(); }

        MyWindow&      win_;
        bp::async_pipe pipe_{win_.io_};
        bp::child      child_{
            bp::search_path("dotnet"),
            std::vector<std::string>{"CORE/bin/Debug/net6.0/CORE.dll"},
            bp::std_out > pipe_, //
            bp::std_err.null(),  //
            bp::std_in.null(),   //
            bp::on_exit(std::bind(&AsyncUpdater::on_exit, this,
                                  std::placeholders::_1,
                                  std::placeholders::_2)),
            win_.io_};

        ~AsyncUpdater() {
            std::error_code ec;
            if (child_.running(ec)) {
                Gdk::Display::get_default()->beep();

                child_.terminate(ec);
                std::cerr << "Terminating running child (" << ec.message() << ")" << std::endl;
            }
        }

        std::array<char, 1024> buf_;

        void read_loop() {
            pipe_.async_read_some( //
                asio::buffer(buf_),
                [this](boost::system::error_code ec, size_t n) {
                    std::cerr << "Got " << n << " bytes (" << ec.message() << ")" << std::endl;
                    if (!ec) {
                        win_.appendOutput({buf_.data(), n});
                        read_loop(); // loop
                    } else {
                        pipe_.close();
                    }
                });
        }

        void on_exit(int exitcode, std::error_code ec) {
            win_.appendOutput("(" + std::to_string(exitcode) + " " +
                              ec.message() + ")\n");
            win_.btnStart_.set_state(Gtk::StateType::STATE_NORMAL);
            win_.btnStop_.set_state(Gtk::StateType::STATE_INSENSITIVE);
        }
    };

    friend struct AsyncUpdater;
    boost::optional<AsyncUpdater> updater_;

    void appendOutput(std::string_view text) {
        auto txt = lblOutput_.get_text();
        txt.append(text.data(), text.size());
        lblOutput_.set_text(std::move(txt));
    }

    bool tick() {
        if (io_.stopped()) {
            std::cerr << "Self-deregistering tick callback" << std::endl;
            tick_source_ = 0;
            return false;
        }
        io_.poll/*_one*/(); // integrate Asio execution context event loop
        return true;
    }
};

MyWindow::MyWindow() {
    set_title("Async Child Process");
    set_default_size(600, 600);

    add(box_);
    box_.add(btnStart_);
    box_.add(lblOutput_);
    box_.add(btnStop_);

    lblOutput_.set_vexpand(true);
    btnStop_.set_state(Gtk::StateType::STATE_INSENSITIVE);

    show_all();

    btnStart_.signal_clicked().connect(sigc::mem_fun(*this, &MyWindow::StartUpdater));
    btnStop_.signal_clicked().connect(sigc::mem_fun(*this, &MyWindow::StopUpdater));

    // wrapper... C compatibility is fun
    GSourceFunc gtick = [](void* data) -> gboolean {
        return static_cast<MyWindow*>(data)->tick();
    };
    tick_source_ = ::g_timeout_add(10, gtick, this);
}

MyWindow::~MyWindow() {
    if (tick_source_) {
        ::g_source_remove(tick_source_);
    }

    updater_.reset();
    work_.reset();
    io_.run();
}

void MyWindow::StartUpdater() {
    lblOutput_.set_text("");
    btnStart_.set_state(Gtk::StateType::STATE_INSENSITIVE);
    btnStop_.set_state(Gtk::StateType::STATE_NORMAL);

    updater_.emplace(*this);
}

void MyWindow::StopUpdater() {
    updater_.reset();
}

int main() {
    auto app = Gtk::Application::create("org.gtkmm.examples.base");

    MyWindow win;

    return app->run(win);
}

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM