私はPythonおよびマルチスレッドプログラミング全般に非常に不慣れです。基本的に、ファイルを別の場所にコピーするスクリプトがあります。これを別のスレッドに配置して、....
を出力して、スクリプトがまだ実行中であることを示すことができるようにします。
私が抱えている問題は、ファイルをコピーできない場合、例外がスローされることです。メインスレッドで実行している場合、これは問題ありません。ただし、次のコードを使用しても機能しません。
try:
threadClass = TheThread(param1, param2, etc.)
threadClass.start() ##### **Exception takes place here**
except:
print "Caught an exception"
スレッドクラス自体で、例外を再スローしようとしましたが、動作しません。私はここの人々が同様の質問をするのを見ましたが、彼らはすべて私がやろうとしていることよりも具体的なことをしているようです(そして提供された解決策をよく理解していません)。私は人々がsys.exc_info()
の使用法に言及しているのを見たことがありますが、どこでどのように使用するのかわかりません。
すべてのヘルプは大歓迎です!
EDIT:スレッドクラスのコードは次のとおりです。
class TheThread(threading.Thread):
def __init__(self, sourceFolder, destFolder):
threading.Thread.__init__(self)
self.sourceFolder = sourceFolder
self.destFolder = destFolder
def run(self):
try:
shul.copytree(self.sourceFolder, self.destFolder)
except:
raise
問題は、thread_obj.start()
がすぐに戻ることです。生成した子スレッドは、独自のスタックで独自のコンテキストで実行されます。そこで発生する例外はすべて、子スレッドのコンテキストにあり、独自のスタックにあります。この情報を親スレッドに伝えるために今考えることができる方法の1つは、何らかのメッセージの受け渡しを使用することです。
サイズについてはこれを試してください:
import sys
import threading
import Queue
class ExcThread(threading.Thread):
def __init__(self, bucket):
threading.Thread.__init__(self)
self.bucket = bucket
def run(self):
try:
raise Exception('An error occured here.')
except Exception:
self.bucket.put(sys.exc_info())
def main():
bucket = Queue.Queue()
thread_obj = ExcThread(bucket)
thread_obj.start()
while True:
try:
exc = bucket.get(block=False)
except Queue.Empty:
pass
else:
exc_type, exc_obj, exc_trace = exc
# deal with the exception
print exc_type, exc_obj
print exc_trace
thread_obj.join(0.1)
if thread_obj.isAlive():
continue
else:
break
if __== '__main__':
main()
concurrent.futures
モジュールを使用すると、別々のスレッド(またはプロセス)で作業を行い、結果の例外を簡単に処理できます。
import concurrent.futures
import shutil
def copytree_with_dots(src_path, dst_path):
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
# Execute the copy on a separate thread,
# creating a future object to track progress.
future = executor.submit(shutil.copytree, src_path, dst_path)
while future.running():
# Print pretty dots here.
pass
# Return the value returned by shutil.copytree(), None.
# Raise any exceptions raised during the copy process.
return future.result()
concurrent.futures
はPython 3.2に含まれており、以前のバージョンでは バックポートfutures
モジュール として利用できます。
別のスレッドでスローされた例外を直接キャッチすることはできませんが、この機能に非常に近いものを非常に透過的に取得するコードを次に示します。子スレッドは、threading.Thread
の代わりにExThread
クラスをサブクラス化する必要があり、親スレッドは、スレッドがジョブを終了するのを待つときにchild_thread.join_with_exception()
の代わりにchild_thread.join()
メソッドを呼び出す必要があります.
この実装の技術的な詳細:子スレッドが例外をスローすると、Queue
を介して親に渡され、親スレッドで再びスローされます。このアプローチでは、忙しい待機時間はありません。
#!/usr/bin/env python
import sys
import threading
import Queue
class ExThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.__status_queue = Queue.Queue()
def run_with_exception(self):
"""This method should be overriden."""
raise NotImplementedError
def run(self):
"""This method should NOT be overriden."""
try:
self.run_with_exception()
except BaseException:
self.__status_queue.put(sys.exc_info())
self.__status_queue.put(None)
def wait_for_exc_info(self):
return self.__status_queue.get()
def join_with_exception(self):
ex_info = self.wait_for_exc_info()
if ex_info is None:
return
else:
raise ex_info[1]
class MyException(Exception):
pass
class MyThread(ExThread):
def __init__(self):
ExThread.__init__(self)
def run_with_exception(self):
thread_name = threading.current_thread().name
raise MyException("An error in thread '{}'.".format(thread_name))
def main():
t = MyThread()
t.start()
try:
t.join_with_exception()
except MyException as ex:
thread_name = threading.current_thread().name
print "Caught a MyException in thread '{}': {}".format(thread_name, ex)
if __== '__main__':
main()
この質問には本当に奇妙に複雑な答えがたくさんあります。私はこれを単純化しすぎていますか?.
from threading import Thread
class PropagatingThread(Thread):
def run(self):
self.exc = None
try:
if hasattr(self, '_Thread__target'):
# Thread uses name mangling prior to Python 3.
self.ret = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
else:
self.ret = self._target(*self._args, **self._kwargs)
except BaseException as e:
self.exc = e
def join(self):
super(PropagatingThread, self).join()
if self.exc:
raise self.exc
return self.ret
いずれかのバージョンのPythonでのみ実行することが確実な場合は、run()
メソッドをマングルバージョンに減らすことができます(_のバージョンでのみ実行する場合Python 3より前)、または単にクリーンバージョン(3以降のPythonのバージョンでのみ実行する場合)。
使用例:
def f(*args, **kwargs)
print(args)
print(kwargs)
raise Exception('I suck')
t = PropagatingThread(target=f, args=(5,), kwargs={'hello':'world'})
t.start()
t.join()
また、参加すると、他のスレッドで例外が発生します。
これは厄介な小さな問題であり、私は自分の解決策を投入したいと思います。私が見つけた他の解決策(たとえばasync.io)は有望に見えましたが、少し黒い箱を提示しました。キュー/イベントループアプローチは、ある種の実装に結び付けます。 ただし、同時フューチャのソースコードは約1000行であり、理解しやすい 。これにより、問題を簡単に解決できました。多くのセットアップなしでアドホックワーカースレッドを作成し、メインスレッドで例外をキャッチできるようになりました。
私のソリューションでは、concurrent futures APIとthreading APIを使用しています。スレッドと未来の両方を提供するワーカーを作成できます。そのようにして、スレッドに参加して結果を待つことができます。
worker = Worker(test)
thread = worker.start()
thread.join()
print(worker.future.result())
...または、完了時にワーカーにコールバックを送信させることができます:
worker = Worker(test)
thread = worker.start(lambda x: print('callback', x))
...またはイベントが完了するまでループできます:
worker = Worker(test)
thread = worker.start()
while True:
print("waiting")
if worker.future.done():
exc = worker.future.exception()
print('exception?', exc)
result = worker.future.result()
print('result', result)
break
time.sleep(0.25)
コードは次のとおりです。
from concurrent.futures import Future
import threading
import time
class Worker(object):
def __init__(self, fn, args=()):
self.future = Future()
self._fn = fn
self._args = args
def start(self, cb=None):
self._cb = cb
self.future.set_running_or_notify_cancel()
thread = threading.Thread(target=self.run, args=())
thread.daemon = True #this will continue thread execution after the main thread runs out of code - you can still ctrl + c or kill the process
thread.start()
return thread
def run(self):
try:
self.future.set_result(self._fn(*self._args))
except BaseException as e:
self.future.set_exception(e)
if(self._cb):
self._cb(self.future.result())
...およびテスト関数:
def test(*args):
print('args are', args)
time.sleep(2)
raise Exception('foo')
concurrent.futures.as_completed
https://docs.python.org/3.7/library/concurrent.futures.html#concurrent.futures.as_completed
次の解決策:
Queue
ソース:
#!/usr/bin/env python3
import concurrent.futures
import time
def func_that_raises(do_raise):
for i in range(3):
print(i)
time.sleep(0.1)
if do_raise:
raise Exception()
for i in range(3):
print(i)
time.sleep(0.1)
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
futures = []
futures.append(executor.submit(func_that_raises, False))
futures.append(executor.submit(func_that_raises, True))
for future in concurrent.futures.as_completed(futures):
print(repr(future.exception()))
可能な出力:
0
0
1
1
2
2
0
Exception()
1
2
None
残念ながら、先物を殺して他の人が失敗するとキャンセルすることはできません。
concurrent.features
; Python:concurrent.futuresキャンセル可能にする方法threading
: スレッドを殺す方法はありますか?次のようなことをする場合:
for future in concurrent.futures.as_completed(futures):
if future.exception() is not None:
raise future.exception()
その後、with
はそれをキャッチし、2番目のスレッドが終了するのを待ってから続行します。以下は同様に動作します。
for future in concurrent.futures.as_completed(futures):
future.result()
future.result()
は例外が発生した場合に例外を再度発生させるためです。
Pythonプロセス全体を終了する場合は、 os._exit(0)
で済ますことができますが、これはおそらくリファクタリングが必要であることを意味します。
完全な例外セマンティクスを備えたカスタムクラス
一度に実行するスレッドの最大数を制限する正しい方法? セクション「エラー処理を備えたキューの例」で、自分にぴったりのインターフェイスをコーディングしました。このクラスは、便利であり、送信と結果/エラー処理を完全に制御することを目的としています。
Python 3.6.7、Ubuntu 18.04でテスト済み。
スレッディングの初心者として、Mateusz Kobosのコード(上記)を実装する方法を理解するのに長い時間がかかりました。以下は、使用方法を理解するのに役立つ明確なバージョンです。
#!/usr/bin/env python
import sys
import threading
import Queue
class ExThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.__status_queue = Queue.Queue()
def run_with_exception(self):
"""This method should be overriden."""
raise NotImplementedError
def run(self):
"""This method should NOT be overriden."""
try:
self.run_with_exception()
except Exception:
self.__status_queue.put(sys.exc_info())
self.__status_queue.put(None)
def wait_for_exc_info(self):
return self.__status_queue.get()
def join_with_exception(self):
ex_info = self.wait_for_exc_info()
if ex_info is None:
return
else:
raise ex_info[1]
class MyException(Exception):
pass
class MyThread(ExThread):
def __init__(self):
ExThread.__init__(self)
# This overrides the "run_with_exception" from class "ExThread"
# Note, this is where the actual thread to be run lives. The thread
# to be run could also call a method or be passed in as an object
def run_with_exception(self):
# Code will function until the int
print "sleeping 5 seconds"
import time
for i in 1, 2, 3, 4, 5:
print i
time.sleep(1)
# Thread should break here
int("str")
# I'm honestly not sure why these appear here? So, I removed them.
# Perhaps Mateusz can clarify?
# thread_name = threading.current_thread().name
# raise MyException("An error in thread '{}'.".format(thread_name))
if __== '__main__':
# The code lives in MyThread in this example. So creating the MyThread
# object set the code to be run (but does not start it yet)
t = MyThread()
# This actually starts the thread
t.start()
print
print ("Notice 't.start()' is considered to have completed, although"
" the countdown continues in its new thread. So you code "
"can tinue into new processing.")
# Now that the thread is running, the join allows for monitoring of it
try:
t.join_with_exception()
# should be able to be replace "Exception" with specific error (untested)
except Exception, e:
print
print "Exceptioon was caught and control passed back to the main thread"
print "Do some handling here...or raise a custom exception "
thread_name = threading.current_thread().name
e = ("Caught a MyException in thread: '" +
str(thread_name) +
"' [" + str(e) + "]")
raise Exception(e) # Or custom class of exception, such as MyException
私はここでパーティーに少し遅れていることを知っていますが、私は非常に似た問題を抱えていましたが、tkinterをGUIとして使用することを含んでおり、メインループは.join()に依存するソリューションを使用することを不可能にしましたしたがって、元の質問の編集で与えられた解決策を採用しましたが、他の人が理解しやすいように、より一般的なものにしました。
動作中の新しいスレッドクラスを次に示します。
import threading
import traceback
import logging
class ExceptionThread(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
def run(self):
try:
if self._target:
self._target(*self._args, **self._kwargs)
except Exception:
logging.error(traceback.format_exc())
def test_function_1(input):
raise IndexError(input)
if __== "__main__":
input = 'useful'
t1 = ExceptionThread(target=test_function_1, args=[input])
t1.start()
もちろん、ログからの例外を印刷する、コンソールに出力するなど、他の方法で例外をいつでも処理させることができます。
これにより、特別な変更を加えることなく、Threadクラスとまったく同じようにExceptionThreadクラスを使用できます。
RickardSjogrenのQueueやsysなどのない同様の方法ですが、シグナルに対するリスナーもありません:例外ブロックに対応する例外ハンドラを直接実行します。
#!/usr/bin/env python3
import threading
class ExceptionThread(threading.Thread):
def __init__(self, callback=None, *args, **kwargs):
"""
Redirect exceptions of thread to an exception handler.
:param callback: function to handle occured exception
:type callback: function(thread, exception)
:param args: arguments for threading.Thread()
:type args: Tuple
:param kwargs: keyword arguments for threading.Thread()
:type kwargs: dict
"""
self._callback = callback
super().__init__(*args, **kwargs)
def run(self):
try:
if self._target:
self._target(*self._args, **self._kwargs)
except BaseException as e:
if self._callback is None:
raise e
else:
self._callback(self, e)
finally:
# Avoid a refcycle if the thread is running a function with
# an argument that has a member that points to the thread.
del self._target, self._args, self._kwargs, self._callback
Self._callbackとrun()のexceptブロックのみが通常のthreading.Threadに追加されます。
裸の例外を使用することは、通常は思いがけないほど多くをキャッチするため、良い習慣ではありません。
except
を変更して、処理したい例外のみをキャッチすることをお勧めします。外側のTheThread
をインスタンス化するときにtry
をインスタンス化すると、例外が発生した場合、割り当てが発生しなくなるため、それを上げると望ましい効果があるとは思いません。
代わりに、次のようにアラートを発して先に進むことができます。
def run(self):
try:
shul.copytree(self.sourceFolder, self.destFolder)
except OSError, err:
print err
その後、その例外がキャッチされると、そこで例外を処理できます。次に、外側のtry
がTheThread
から例外をキャッチすると、それが既に処理したものではないことがわかり、プロセスフローを分離するのに役立ちます。
私が好きな方法の1つは、 observer pattern に基づいています。スレッドがリスナーに例外を発行するために使用するシグナルクラスを定義します。スレッドから値を返すためにも使用できます。例:
import threading
class Signal:
def __init__(self):
self._subscribers = list()
def emit(self, *args, **kwargs):
for func in self._subscribers:
func(*args, **kwargs)
def connect(self, func):
self._subscribers.append(func)
def disconnect(self, func):
try:
self._subscribers.remove(func)
except ValueError:
raise ValueError('Function {0} not removed from {1}'.format(func, self))
class WorkerThread(threading.Thread):
def __init__(self, *args, **kwargs):
super(WorkerThread, self).__init__(*args, **kwargs)
self.Exception = Signal()
self.Result = Signal()
def run(self):
if self._Thread__target is not None:
try:
self._return_value = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
except Exception as e:
self.Exception.emit(e)
else:
self.Result.emit(self._return_value)
if __== '__main__':
import time
def handle_exception(exc):
print exc.message
def handle_result(res):
print res
def a():
time.sleep(1)
raise IOError('a failed')
def b():
time.sleep(2)
return 'b returns'
t = WorkerThread(target=a)
t2 = WorkerThread(target=b)
t.Exception.connect(handle_exception)
t2.Result.connect(handle_result)
t.start()
t2.start()
print 'Threads started'
t.join()
t2.join()
print 'Done'
私はこれが完全に安全な方法であると主張するのに十分なスレッドの操作経験がありません。しかし、それは私のために働いており、柔軟性が好きです。
例外ストレージでスレッドをラップします。
import threading
import sys
class ExcThread(threading.Thread):
def __init__(self, target, args = None):
self.args = args if args else []
self.target = target
self.exc = None
threading.Thread.__init__(self)
def run(self):
try:
self.target(*self.args)
raise Exception('An error occured here.')
except Exception:
self.exc=sys.exc_info()
def main():
def hello(name):
print(!"Hello, {name}!")
thread_obj = ExcThread(target=hello, args=("Jack"))
thread_obj.start()
thread_obj.join()
exc = thread_obj.exc
if exc:
exc_type, exc_obj, exc_trace = exc
print(exc_type, ':',exc_obj, ":", exc_trace)
main()
スレッドの例外をキャッチして呼び出し元のメソッドとやり取りする簡単な方法は、worker
メソッドに辞書またはリストを渡すことです。
例(辞書をworkerメソッドに渡す):
import threading
def my_method(throw_me):
raise Exception(throw_me)
def worker(shared_obj, *args, **kwargs):
try:
shared_obj['target'](*args, **kwargs)
except Exception as err:
shared_obj['err'] = err
shared_obj = {'err':'', 'target': my_method}
throw_me = "Test"
th = threading.Thread(target=worker, args=(shared_obj, throw_me), kwargs={})
th.start()
th.join()
if shared_obj['err']:
print(">>%s" % shared_obj['err'])