2015年12月13日日曜日

QEventLoopでイベントループを自在に操る

この記事は Qt Advent Calender 2015 の14日の記事です。
QEventLoopクラスを使って非同期処理を同期的に扱う方法を紹介したいと思います。

QEventLoop はQtのイベントループを扱うクラスです。イベントループについては2日目の記事で分かりやすく解説されているので、詳しくない方はまずはこちらを参考にして下さい。

QEventLoop はQApplication::execやQDialog::execなどexecというメソッドを持つクラスの中で使われており、その中でQEventLoop::execが呼ばれて実際にイベントループが実行されます。QEventLoopはpublicなクラスなのでユーザのコードからも呼ぶことができ、これを使うことで非同期処理を同期な方法で呼ぶことができるようになります。実際に例を見てみましょう。

まずはQEventLoopを使わずにシグナルとスロットを用いて非同期処理をシーケンシャルに行うサンプルです。

#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QThread>

class SleepThread : public QThread {
 protected:
  void run() {
    qDebug() << "doing heavy task";
    sleep(1);
  }
};

int main(int argv, char** args) {
  QApplication app(argv, args);

  SleepThread* thread1 = new SleepThread();
  QObject::connect(thread1, &QThread::finished, [&] {
    SleepThread* thread2 = new SleepThread();
    QObject::connect(thread2, &QThread::finished, [&] {
      SleepThread* thread3 = new SleepThread();
      QObject::connect(thread3, &QThread::finished, [&] {
        qDebug() << "finished all tasks!";
        app.exit();
      });
      thread3->start();
    });
    thread2->start();
  });
  thread1->start();
  app.exec();
}

以下の様な出力になります。’doing heavy task’は1秒毎に出力されます。

doing heavy task
doing heavy task
doing heavy task
finished all tasks!

SleepThreadはrunの中で何か重い処理をしていると仮定して、ここでは単に1秒スリープしています。SleepThreadのfinishedシグナルにC++11のラムダ関数をconnectしてスレッドの処理が終了したらラムダ関数が実行され、次のSleepThreadを生成・実行しています。3つ全ての非同期処理が終わったら”finished all tasks!”を表示して終了します。
ご覧のとおりネストが深くなって少々読みづらいですよね。これをQEventLoopを使って同期的に書き直してみます。

#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QThread>
#include <QEventLoop>

class SleepThread : public QThread {
 protected:
  void run() {
    qDebug() << "doing heavy task";
    sleep(1);
  }
};

int main(int argv, char** args) {
  QApplication app(argv, args);

  SleepThread* thread1 = new SleepThread();
  QEventLoop loop1;
  QObject::connect(thread1, &QThread::finished, &loop1, &QEventLoop::quit);
  thread1->start();
  loop1.exec();

  SleepThread* thread2 = new SleepThread();
  QEventLoop loop2;
  QObject::connect(thread2, &QThread::finished, &loop2, &QEventLoop::quit);
  thread2->start();
  loop2.exec();

  SleepThread* thread3 = new SleepThread();
  QEventLoop loop3;
  QObject::connect(thread3, &QThread::finished, &loop3, &QEventLoop::quit);
  thread3->start();
  loop3.exec();

  qDebug() << "finished all tasks!";
}

出力はもちろんさっきと同じです。
QThead::finishedシグナルをQEventLoop::quitスロットにconnectして、スレッドが終了したらイベントループも終了するようにしています。その後QEventLoop::execを実行してイベントループに入ります。こうすることで非同期処理を行っているにも関わらず同期的に記述でき、プログラムもすっきりしました。さらにこの処理を関数として抽出すればもっと読みやすくできそうです。
QEventLoop::execを実行した後は見かけ上そこで処理が止まったようになりますが、実際はイベントループが回っているのでマウスやキーボードのイベントもその中で通常通り処理され、フリーズしたりはしません。

ネストしたイベントループ

このようにQEventLoopを使うことでいつでもイベントループを実行できることを確認しました。次はイベントループを実行した中でさらにイベントループを実行する例を見てみます。

main.cpp

#include <QApplication>
#include <QDebug>
#include <QObject>
#include "SleepThread.h"

int main(int argv, char** args) {
  QApplication app(argv, args);

  SleepThread* thread = new SleepThread();
  QObject::connect(thread, &QThread::finished, &app, &QApplication::quit);
  thread->moveToThread(thread);
  thread->start();
  QMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 3000));
  QMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 5000));
  app.exec();
}

main関数はSleepThread(先ほどのSleepThreadとは違います)を作成し、finishedシグナルにQApplication::quitをconnectしています。その後thread->moveToThread(thread) でthreadオブジェクトのthread affinityをSleepThreadに移します。
その後thread->start()でスレッドのイベントループを実行します。(今回QThread::runをオーバーライドしていないので、デフォルト動作のQThread::execが実行されます。)
QMetaObject::invokeMethodでSleepThreadのsleepメソッドを非同期で呼び出します。先ほどthread->moveToThread(thread) でthreadオブジェクトのthread affnityをSleepThreadに移したので、invokeMethodを呼び出した時にQt::QueuedConnectionが使われます。invokeMethodの詳細はQtのドキュメントを確認して下さい。
http://doc.qt.io/qt-5/qmetaobject.html#invokeMethod

invokeMethodでSleepThreadのsleepメソッドを2回非同期で呼び出した後app.exec()でQApplicationのイベントループに入ります。
次はSleepThreadの中身を見てみます。

SleepThread.h

#pragma once

#include <QThread>
#include <QEventLoop>
#include <QTimer>

class SleepThread : public QThread {
  Q_OBJECT
 public slots:
  void sleep(unsigned long msec) {
    QEventLoop loop;
    qDebug() << "start timer with" << msec << "[msec]";
    QTimer::singleShot(msec, &loop, [&loop, msec]{
      qDebug() << "finished waiting" << msec << "[msec]";
      loop.quit();
    });
    loop.exec();
    qDebug() << "event loop of" << msec << "[msec] finished";
    exit();
  }
};

sleepメソッドはQEventLoopでイベントループを実行しています。QTimer::singleShotを使ってmsecミリ秒後にイベントループを終了しています。今回なぜsleep(msec)としなかったかというと、sleep(msec)は現在のスレッドをブロックしてしまうため、SleepThreadで回っているイベントループを止めてしまうためです。
イベントループ終了後QThread::exitを呼び出してスレッドを終了します。

出力は以下のようになります。

start timer with 3000 [msec]
start timer with 5000 [msec]
finished waiting 3000 [msec]
finished waiting 5000 [msec]
event loop of 5000 [msec] finished
event loop of 3000 [msec] finished

イベントループはスレッドごとに存在していて、thread->start()でSleepThreadのイベントループが始まります。
次にQMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 3000)); を呼び出すとQt::QueuedConnectionの時に内部ではQMetaCallEventというイベントがSleepThreadのイベントループで発生し、sleep(3000) の形でメソッドが呼ばれます。sleepメソッドの中ではさらにQEventLoop::execでイベントループを実行しているため、イベントループがネストした形になります。
その後QMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 5000)); でsleep(5000)が実行され、さらに内部でイベントループが実行されます。

QThread::exec()で作られた親イベントループ
  sleep(3000)の中で作られた子イベントループ
    sleep(5000)の中で作られた孫イベントループ

このように3つイベントループがネストしていますが、実際にイベントを処理するのは常に一番深いイベントループで、他のイベントループは止まった状態になっています。この例ではsleep(3000)で作られた子イベントループの方がタイムアウト時間が短いので先にloop.quit(); が呼ばれますが、QEventLoop::quitを呼んでもこの子イベントループは止まっているので実際には即座には終了しません。その2秒後にsleep(5000)のQTimerのタイムアウトが発生しloop.quit(); が呼ばれ、sleep(5000)で作られた孫イベントループが終了します。
その後sleep(3000)の子イベントループが再開しますが、すでに孫イベントループの中でloop.quit()が呼ばれているので即座に終了します。
そしてようやく親イベントループが再開しますが、こちらもすでにsleepメソッドの中でQThread::exitが呼ばれているので、再開しても即座に終了します。

シグナル・スロットを用いた非同期処理とQEventLoopは相性がいいので、色々な場面で役立つこと間違いなしです!
明日はasobotさんのQtCreatorとqmakeについてです。

参考

2015年11月17日火曜日

アロー関数はコンストラクタとして使えない

util.inheritsでアロー関数を渡すとエラーになるので調査していたところ、アロー関数はコンストラクタとして使えないことが分かった。
原因はアロー関数のprototypeがundefinedになるため。

再現コード

'use strict';
var util = require('util');
var EventEmitter = require('events');

function InputDialog() { }

console.log('InputDialog prototype: ' + InputDialog.prototype)
util.inherits(InputDialog, EventEmitter);

const InputDialog2 = () => { }

console.log('InputDialog2 prototype: ' + InputDialog2.prototype)
util.inherits(InputDialog2, EventEmitter);

出力

InputDialog prototype: [object Object]
InputDialog2 prototype: undefined
util.js:764
  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
         ^

TypeError: Object.setPrototypeOf called on null or undefined
    at Function.setPrototypeOf (native)
    at Object.exports.inherits (util.js:764:10)
    at Object.<anonymous> (/Users/shinichi/code/silkedit/test.js:13:6)
    at Module._compile (module.js:425:26)
    at Object.Module._extensions..js (module.js:432:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:457:10)
    at startup (node.js:136:18)
    at node.js:972:3

util.inheritsのコード
https://github.com/nodejs/node/blob/v5.1.0/lib/util.js

exports.inherits = function(ctor, superCtor) {

  if (ctor === undefined || ctor === null)
    throw new TypeError('The constructor to `inherits` must not be ' +
                        'null or undefined.');

  if (superCtor === undefined || superCtor === null)
    throw new TypeError('The super constructor to `inherits` must not ' +
                        'be null or undefined.');

  if (superCtor.prototype === undefined)
    throw new TypeError('The super constructor to `inherits` must ' +
                        'have a prototype.');

  ctor.super_ = superCtor;
  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
};

参考

Arrow functions versus normal functions
An arrow function is different from a normal function in only three ways: First, it always has a bound this. Second, it can’t be used as a constructor: There is no internal method [[Construct]] (that allows a normal function to be invoked via new) and no property prototype. Therefore, new (() => {}) throws an error. Third, as arrow functions are an ECMAScript.next-only construct, they can rely on new-style argument handling (parameter default values, rest parameters, etc.) and don’t support the special variable arguments. Nor do they have to, because the new mechanisms can do everything that arguments can.

Node.jsのバージョン間での挙動の違い

ちなみにv0.12.0はアロー関数をコンストラクタとしてutil.inheritsに渡しても動作する。
v5.1.0ではダメ。

2015年3月27日金曜日

QLocalSocket+QEventLoopを組み合わせた時のreadyReadシグナルの仕様

QLocalSocketとQEventLoopを組み合わせると、非同期なネットワーク呼び出しを同期的に記述できます。(もちろんUIスレッドはブロックしません)。

詳細はここの”Forcing event dispatching”という箇所を参考にして下さい。
http://qt-project.org/wiki/Threads_Events_QObjects

基本的な流れとしてはQLocalSocketにデータを何かwriteした後ローカルのQEventLoopを呼び出し(exec()というメソッド)、readyReadシグナルが届いたらイベントループを終了(quit()メソッド)させるという流れです。

しかしここで大きな落とし穴があり、readyReadシグナルが呼ばれた中で再びイベントループを呼び出すと、readyReadシグナルはそのイベントループでは呼び出されません。Qtのドキュメントにもそのように書かれています。(僕はこの仕様でかなりはまりました^^;)

http://doc.qt.io/qt-5/qiodevice.html#readyRead

readyRead() is not emitted recursively; if you reenter the event loop or call waitForReadyRead() inside a slot connected to the readyRead() signal, the signal will not be reemitted (although waitForReadyRead() may still return true).

実際のソースコードの該当箇所を見てみると以下のようになっています。

qabstractsocket.cpp

// only emit readyRead() when not recursing, and only if there is data available
bool hasData = newBytes > 0
#ifndef QT_NO_UDPSOCKET
    || (!isBuffered && socketType != QAbstractSocket::TcpSocket && socketEngine && socketEngine->hasPendingDatagrams())
#endif
    || (!isBuffered && socketType == QAbstractSocket::TcpSocket && socketEngine)
    ;

if (!emittedReadyRead && hasData) {
    QScopedValueRollback<bool> r(emittedReadyRead);
    emittedReadyRead = true;
    emit q->readyRead();
}

readyReadシグナルにDirectConnectionを使ってconnectされたスロットが呼び出され(この時emittedReadyRead == trueとなる)、その中でイベントループが作られ再度上記の箇所に到達した場合、emittedReadyReadはすでにtrueなので再度emitされないという仕組みです。イベントループが終了するとQScopedValueRollbackによりemittedReadyReadの値がfalseに戻され、再びemitされるようになります。

この仕様を実験するため以下のコードを書きました。
QLocalServer、QLocalSocketを使ってサーバ、クライアント間でUnix Domain Socketを介してデータを相互に送受信しています。
動作確認した環境はMac (Mavericks) , Qt 5.4です。
readyReadシグナルにconnectされたonReadyReadスロットの中でQEvnetLoopを作成、実行しています。

サーバ側のコード

Server.h

#pragma once

#include <QLocalServer>
#include <QLocalSocket>
#include <QFile>
#include <QEventLoop>
#include <QObject>

class Result : public QObject {
  Q_OBJECT

  int m_result;

 public:
  int result() { return m_result; }
  void setResult(int result) {
    m_result = result;
    emit ready();
  }

signals:
  void ready();
};

class Server : public QObject {
  Q_OBJECT

  Result m_result1;
  Result m_result3;
  QLocalSocket* m_sock;

 public:
  void start() {
    QString sockPath = "/tmp/qeventloop_test";
    QLocalServer* server = new QLocalServer();
    QFile sockFile(sockPath);
    if (sockFile.exists()) {
      sockFile.remove();
    }
    server->listen(sockPath);
    connect(server, &QLocalServer::newConnection, [this, server]() {
      m_sock = server->nextPendingConnection();
      connect(m_sock, &QLocalSocket::disconnected, m_sock, &QLocalSocket::deleteLater);
      QObject::connect(
          m_sock, &QLocalSocket::readyRead, this, &Server::onReadyRead, Qt::QueuedConnection);

      sendData(m_sock, 1);

      QEventLoop loop;
      connect(&m_result1, &Result::ready, &loop, &QEventLoop::quit);
      qDebug("start event loop to wait for 1");
      loop.exec();
      qDebug("end event loop to wait for 1");
    });
  }

  void onReadyRead() {
    qDebug("bytesAvailable: %lld", m_sock->bytesAvailable());
    qint64 bytesAvailable = m_sock->bytesAvailable();
    QByteArray buffer = m_sock->readAll();
    QDataStream ds(buffer);
    while (bytesAvailable > 0) {
      int num;
      ds >> num;
      qDebug("received %d", num);
      bytesAvailable -= 4;
      if (num == 2) {
        sendData(m_sock, 3);

        QEventLoop loop;
        QObject::connect(&m_result3, &Result::ready, &loop, &QEventLoop::quit);
        qDebug("start event loop to wait for 3");
        loop.exec();
        qDebug("end event loop to wait for 3");

      } else if (num == -1) {
        m_result1.setResult(num);
      } else if (num == -3) {
        m_result3.setResult(num);
      }
    }
  }

  void sendData(QLocalSocket* sock, int num) {
    qDebug("send %d", num);
    QByteArray block;
    QDataStream ds(&block, QIODevice::WriteOnly);
    ds << num;
    sock->write(block);
  }
};

main.cpp

#include <QApplication>
#include <QDebug>
#include <QObject>
#include "Server.h"

int main(int argv, char** args) {
  QApplication app(argv, args);

  Server server;
  server.start();

  return app.exec();
}

クライアント側のコード

main.cpp

#include <QDebug>
#include <QLocalSocket>
#include <QApplication>

void sendData(QLocalSocket& sock, int num) {
  qDebug("send %d", num);
  QByteArray block;
  QDataStream ds(&block, QIODevice::WriteOnly);
  ds << num;
  sock.write(block);
}

int main(int argv, char** args) {
  QApplication app(argv, args);

  QLocalSocket sock;
  QObject::connect(&sock, &QLocalSocket::readyRead, [&sock]() {
    qint64 bytesAvailable = sock.bytesAvailable();
    QByteArray buffer = sock.readAll();
    QDataStream ds(buffer);
    while (bytesAvailable > 0) {
      int num;
      ds >> num;

      qDebug("received %d", num);
      bytesAvailable -= 4;

      if (num == 1) {
        sendData(sock, 2);
        sendData(sock, -1);
      } else if (num == 3) {
        sendData(sock, -3);
      }
    }
  });

  sock.connectToServer("/tmp/qeventloop_test");
  return app.exec();
}

実行結果は以下のようになります。

send 1
start event loop to wait for 1
bytesAvailable: 8
received 2
send 3
start event loop to wait for 3
bytesAvailable: 4
received -3
end event loop to wait for 3
received -1
end event loop to wait for 1

次にサーバ側プログラムの以下のQueuedConnectionとなっている箇所を

QObject::connect(m_sock, &QLocalSocket::readyRead, this, &Server::onReadyRead, Qt::QueuedConnection);

以下のようにDirectConnection(デフォルトではsenderとreceiverが同一スレッドであればDirectConnectionとなります)を使うように変更してみます。

      QObject::connect(
          m_sock, &QLocalSocket::readyRead, this, &Server::onReadyRead);

実行結果は以下のようになります。クライアントが-3を送信してもサーバのQLocalSocketはreadyReadシグナルが呼び出されないので、以下のように永遠にイベントループが終了しません。

send 1
start event loop to wait for 1
bytesAvailable: 8
received 2
send 3
start event loop to wait for 3

QLocalSocket, QEventLoopを使う時はreadyReadシグナルの仕様に気をつけましょう!

2015年2月7日土曜日

日本からBank of Americaの口座を閉じる

昨年10月にアメリカから帰国したんですが、まだ一部ペンディングのトランザクションがあって帰国前にアメリカの口座を閉じれず、帰国後に閉じたんですがすごく時間がかかって大変でした。最初に電話で問い合わせてから小切手を受け取るまでに3ヶ月半かかりました。以下は簡単な流れです。今後同様のことを行う方の参考になれば幸いです。

10月頭

帰国

10月中旬

電話でCheckingとSavingsアカウントを閉じたいと伝える。残高は小切手(Cashier’s check)を送るから日本の住所を教えてくれと言われ教える。小切手は2週間くらいで届くとのこと。

11月上旬

2週間たっても小切手が届かないので再度電話で問い合わせ。前回の電話で閉じれたのだと思っていたらどうやらCheckingアカウントは残高がかなりあるため電話では受け付けできず、口座を閉じたいという旨の手紙を送れと言われる。

12月中旬

1か月後ようやく返信が来て小切手が届いたのかと思いきや、口座を閉じるなら所定のフォームに公証人のサインをして返信しろという旨の封書が届く。電話で問い合わせたところ日本の公証人でOKらしかったので、すぐに公証人にサインをもらって返信。

1月中旬

ようやく小切手が届いたので、UFJに外貨預金口座を開設して小切手の取り立てを依頼。入金までに3週間程かかるとのこと。

2月上旬

きっちり3週間後外貨預金口座に入金される。

入金まで約3ヶ月半かかりました。良い子のみんなは帰国前に口座を閉じるか、当面の生活費をワイヤートランスファーなどで帰国前に送金しておきましょう。。

2015年1月27日火曜日

Artistic License 2.0のソフトを自作アプリと一緒に配布するための条件

npmを現在開発中のエディタに埋め込んで一緒にバイナリを配布したいので、npmが使用しているArtistic License 2.0について調べてみました。
主にPerlで使われているようです。
http://ja.wikipedia.org/wiki/Artistic_License

原文
http://www.perlfoundation.org/artistic_license_2_0

埋め込む時のポイントは原文の以下の(7)と(8)です。

Aggregating or Linking the Package

(7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation.

(8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package.

Aggregateする場合は商用ソフトにも含めることができ(ただしnpmはArtistic Licenseのまま)、Linkする場合はnpmへのdirect interfaceを提供しなければ商用ソフトにもリンクして配布できそうです。

direct interfaceとは何でしょう?Artistic Licenseの補足のSection 8に説明があります。

http://www.perlfoundation.org/artistic_2_0_notes

Section 8:

You’re totally free to embed Perl inside your software in a way that doesn’t allow users to access Perl. On the other hand, if you embed it and users can nevertheless do things like running Perl scripts, then you really are distributing Perl and need make sure that your distribution fits one of the use cases in (1)-(7).

ユーザが直接npmのプログラムを実行できたらそれはdirect interfaceと見なされるみたいです。

AggregateとLinkの違いは何でしょう?
GNU GPLのFAQにaggregateの説明があります。

http://www.gnu.org/licenses/gpl-faq.html#MereAggregation

What is the difference between an “aggregate” and other kinds of “modified versions”?

An “aggregate” consists of a number of separate programs, distributed together on the same CD-ROM or other media. The GPL permits you to create and distribute an aggregate, even when the licenses of the other software are non-free or GPL-incompatible. The only condition is that you cannot release the aggregate under a license that prohibits users from exercising rights that each program’s individual license would grant them.

Where’s the line between two separate programs, and one program with two parts? This is a legal question, which ultimately judges will decide. We believe that a proper criterion depends both on the mechanism of communication (exec, pipes, rpc, function calls within a shared address space, etc.) and the semantics of the communication (what kinds of information are interchanged).

If the modules are included in the same executable file, they are definitely combined in one program. If modules are designed to run linked together in a shared address space, that almost surely means combining them into one program.

By contrast, pipes, sockets and command-line arguments are communication mechanisms normally used between two separate programs. So when they are used for communication, the modules normally are separate programs. But if the semantics of the communication are intimate enough, exchanging complex internal data structures, that too could be a basis to consider the two parts as combined into a larger program.

直接npmを静的or動的リンクせず、パイプやソケットを使ってプロセス間通信を行えばaggregateと見なされるみたいです。

結論

つまりnpmとリンクさせずプロセス間通信を使えばnpmを商用エディタに含めて配布することが可能みたいです。その際npmのバイナリだけでなくライセンスファイルも一緒に含めないといけません。