如何异步与儿童过程进行沟通?

发布于 2025-01-17 09:57:58 字数 1741 浏览 5 评论 0原文

我有一个由GTKMM构建的父级应用程序,我需要产生一个子过程(另一个GUI应用程序)并与之通信。我使用boost ::流程来做到这一点。我知道我应该异步进行,以便不会阻止父ui。

因此,问题:

  • 如何异步聆听儿童应用程序中的任何输出并处理它?
  • 我怎么知道何时关闭/终止儿童应用程序/过程?

这是我当前这样做的方式(正在阻止UI):

#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

I have a parent GUI app built with GTKmm, and I need to spawn a child process (another GUI app) and communicate with it. I use boost::process to do that. I know that I should do it asynchronously, so that the parent UI wouldn't be blocked.

So the questions:

  • How can I asynchronously listen to any output from the child app and process it?
  • How can I know when the child app/process has been closed/terminated?

here is how I currently do it (which is blocking the UI):

#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);
}

This code use GTKmm 3.0 lib

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鸠魁 2025-01-24 09:57:58

正如您所猜测的,Start() 方法会阻塞,因此其他 Gtk 代码都没有机会运行。这意味着什么都没有完成,甚至没有绘制 UI。

相反,让 child 成为该类的成员。接下来,使用 async_pipe 而不是阻塞管道流,这样您就不必阻塞来读取。现在,设置一个异步读取循环来响应来自子进程标准输出的传入数据。

我创建了一个简单的 dotnet core 控制台应用程序来测试这一点:

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 方面

我简化了很多事情。

最棘手的部分是从 Gtk 事件循环中轮询 io_context。我选择使用 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);
}

在此处输入图像描述

As you've guessed, the Start() method blocks, so no other Gtk code gets a chance to run. This means nothing gets done, not even drawing the UI.

Instead, make the child a member of the class. Next, use an async_pipe instead of the blocking pipe stream, so you don't have to block to read either. Now, set-up an async read loop to respond to incoming data from the child process'es standard output.

I've created a simple dotnet core console application to test this with:

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

Now we replace the default Program.cs with:

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

Building and running again prints, over a total timespan of 5 seconds:

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!

Doing The GTK Side

I've simplified many things.

The trickiest part is to make the io_context be polled from the Gtk event loop. I opted to use g_add_timeout for the purpose. It is very important to correctly de-register the tick handler, so no undefined behavior results after MyWindow is destructed.

tick() runs every 10ms (if possible). Perhaps for your use-case you can lower the frequency.

I added a Stop button for good measure, and made sure that Start/Stop buttons are enabled/disabled as appropriate. Let's do some live demo:

Full Demo

#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);
}

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文