質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
並列処理

複数の計算が同時に実行される手法

Qt

QtはGUIプログラムの開発で広く使われているクロスプラットフォーム開発のフレームワークです。

Webサーバー

Webサーバーとは、HTTPリクエストに応じて、クライアントに情報を提供するシステムです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

1724閲覧

Python Qt 5 を用いたブラウザ作成においてスレッドを用いた更新時に発生するエラー。

harukat

総合スコア1

並列処理

複数の計算が同時に実行される手法

Qt

QtはGUIプログラムの開発で広く使われているクロスプラットフォーム開発のフレームワークです。

Webサーバー

Webサーバーとは、HTTPリクエストに応じて、クライアントに情報を提供するシステムです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/06/29 09:45

前提・実現したいこと

ここに質問の内容を詳しく書いてください。
Python 3.8.10 でブラウザとサーバーの並列起動をするシステムを作っています。
ブラウザはQt5で作成し、現在表示されているウィンドウ画像を frame 変数に保持するようにします。サーバーは、その frame 画像を取得することを目的としています。

QtでのGUI実装について明るくないのですが、サーバー側から capture() を呼び、frame を取得することは、以下のようなエラーが発生し、別スレッドからは厳しいようです。

**Cannot make QOpenGLContext current in a different thread**

また、更新処理に該当する update_frame() をバックグラウンドで回し続けるために、 Thread を使ってみましたが、こちらも以下のようなエラーが発生し、不可能でした。

❯ python browser.py QObject::setParent: Cannot set parent, new parent is in a different thread Cannot make QOpenGLContext current in a different thread [1] 7703 abort (core dumped) python browser.py

該当のソースコードは以下になります。

該当のソースコード

python

1# browser.py 2import sys 3from PyQt5.QtCore import Qt 4from PyQt5.QtCore import QUrl 5from PyQt5.QtCore import QTimer 6from PyQt5.QtGui import QPixmap 7from PyQt5.QtGui import QImage 8from PyQt5.QtWebEngineWidgets import QWebEngineView 9from PyQt5.QtWidgets import QApplication 10import numpy as np 11import threading 12 13def qim2cv(qimage): 14 w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth() 15 bytes_ = qimage.bits().asstring(w * h * d // 8) 16 arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8)) 17 return arr 18 19class BrowserApp(): 20 def __init__(self, size=(1000,600), url="https://www.google.com/?hl=ja"): 21 self.app = QApplication(sys.argv) 22 self.window_size = size 23 self.browser = QWebEngineView() 24 self.browser.resize(*self.window_size) 25 self.browser.load(QUrl(url)) 26 self.url = url 27 self.frame = None # 常に最新のブラウザキャプチャを保持したい 28 29 def update_frame(self): 30 while True: 31 self.frame = self.capture() 32 33 def capture(self): 34 ## ブラウザの表示画像からフレームを更新 35 browser_size = self.browser.contentsRect() 36 pixmap_img = QPixmap(browser_size.width(), browser_size.height()) 37 self.browser.render(pixmap_img) 38 return qim2cv(pixmap_img.toImage()) # OpenCV 画像に変換 39 40 def show(self): 41 self.browser.show() 42 self.app.exec_() 43 44 45if __name__ == "__main__": 46 app = BrowserApp() 47 48 # バックグラウンドで更新させたい 49 thread = threading.Thread(target=app.update_frame) 50 thread.start() 51 52 app.show()

試したこと

  • 更新処理を除けば、ブラウザが正常に起動することは確認しました。
  • capture() では、画面のスクリーンショットを撮れることを確認しました。

補足情報(FW/ツールのバージョンなど)

OS: Linux(OpenSUSE Leap 15.3)
Python環境: python 3.8.10(Pyenv)
サーバー側はfastapiを使用しています。

皆様のご助力に感謝いたします。
よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

QtでのGUI実装について明るくないのですが、サーバー側から capture() を呼び、frame を取得することは、以下のようなエラーが発生し、別スレッドからは厳しいようです。

Cannot make QOpenGLContext current in a different thread

また、更新処理に該当する update_frame() をバックグラウンドで回し続けるために、 Thread を使ってみましたが、こちらも以下のようなエラーが発生し、不可能でした。

上記の点はどちらも、スレッドセーフな操作ではない為が含まれている為、
別スレッドから直接呼び出すことは出来ません。

スレッドを使う場合、Qt であれば、シグナル&スロットの仕組みを使い、
GUI 関連の操作はメインスレッド側で呼び出されるように構成して下さい。
別スレッドからはシグナルを使い通知のみ行なえます。

代替策としては、メインスレッドのバックグラウンドでの実行ならば、
まずはタイマーの利用を検討して下さい。

python

1# class 内 ... 略 2 3 def update_frame(self): 4 # NOTE: タイマーで呼び出しの場合は、呼び出し側で繰り返されます。 5 # イベントループに処理が戻らず、ウィンドウが応答なしになるので、内部でループはしない。 6 7 self.frame = self.capture() 8 9 10if __name__ == "__main__": 11 app = BrowserApp() 12 13 # タイマーで 1秒(1000ms) 毎に呼び出し 14 from PyQt5.QtCore import QTimer 15 timer = QTimer(app.app) 16 timer.timeout.connect(app.update_frame) 17 timer.start(1000) 18 19 app.show() 20

capture 内部で時間のかかる処理を行いたい場合は、
「時間の掛かる処理」を別スレッドで行う必要があります。
また、結果をメイン側で受け取る場合にも、スレッド間での通知を使います。

マルチスレッドにする場合は、操作対象のデータがスレッドセーフかどうかに注意。
複数のスレッドから同時に操作しようとすると、エラーなくおかしな挙動になることもあります。
ドキュメントに明記されてない限りは、大抵の操作はスレッドセーフでは有りません。

投稿2021/06/29 12:00

編集2021/07/01 02:48
teamikl

総合スコア8664

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

harukat

2021/06/30 06:38

本当にありがとうございます! 問題なく、動作いたしました。また、解説に関しても非常にわかりやすかったです。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問