Bắt ngoại lệ của một luồng trong luồng người gọi trong Python


207

Tôi rất mới với Python và lập trình đa luồng nói chung. Về cơ bản, tôi có một tập lệnh sẽ sao chép các tập tin vào một vị trí khác. Tôi muốn điều này được đặt trong một luồng khác để tôi có thể xuất ra ....để chỉ ra rằng tập lệnh vẫn đang chạy.

Vấn đề mà tôi gặp phải là nếu các tập tin không thể được sao chép thì nó sẽ tạo ra một ngoại lệ. Điều này là ổn nếu chạy trong luồng chính; tuy nhiên, có mã sau không hoạt động:

try:
    threadClass = TheThread(param1, param2, etc.)
    threadClass.start()   ##### **Exception takes place here**
except:
    print "Caught an exception"

Trong chính lớp xử lý, tôi đã cố gắng ném lại ngoại lệ, nhưng nó không hoạt động. Tôi đã thấy những người ở đây hỏi những câu hỏi tương tự, nhưng tất cả họ dường như đang làm một cái gì đó cụ thể hơn những gì tôi đang cố gắng làm (và tôi không hiểu lắm về các giải pháp được đưa ra). Tôi đã thấy mọi người đề cập đến việc sử dụng sys.exc_info(), tuy nhiên tôi không biết sử dụng nó ở đâu và như thế nào.

Tất cả sự giúp đỡ được đánh giá rất cao!

EDIT: Mã cho lớp luồng bên dưới:

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

Bạn có thể cung cấp cái nhìn sâu sắc hơn về những gì đang xảy ra bên trong TheThread? Mã mẫu có lẽ?
jathanism

Chắc chắn rồi. Tôi sẽ chỉnh sửa phản hồi của tôi ở trên để bao gồm một số chi tiết.
Phanto

1
Bạn đã xem xét chuyển đổi vòng của nó để Chủ đề chính là bit thực hiện công cụ và chỉ báo tiến trình nằm trong Chủ đề được sinh ra?
Dan Head

1
Dan Head, bạn đang đề cập đến chủ đề chính đầu tiên sinh ra chức năng "..." và sau đó chạy chức năng sao chép? Điều đó có thể làm việc và tránh các vấn đề ngoại lệ. Nhưng, tôi vẫn muốn học cách xâu chuỗi đúng cách trong python.
Phanto

Câu trả lời:


114

Vấn đề là thread_obj.start()trả lại ngay lập tức. Chuỗi con mà bạn sinh ra thực thi trong ngữ cảnh riêng của nó, với ngăn xếp riêng của nó. Bất kỳ ngoại lệ nào xảy ra đều có trong ngữ cảnh của luồng con và nó nằm trong ngăn xếp riêng của nó. Một cách tôi có thể nghĩ ra ngay bây giờ để truyền đạt thông tin này đến luồng gốc là sử dụng một số loại thông điệp truyền qua, vì vậy bạn có thể xem xét điều đó.

Hãy thử điều này cho kích thước:

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 __name__ == '__main__':
    main()

5
Tại sao không tham gia chuỗi thay vì vòng lặp xấu xí này? Xem multiprocessingtương đương: gist.github.com/2311116
schlamar

1
Tại sao không sử dụng stackoverflow stackoverflow.com/questions/1092531/event-system-in-python/ tựa dựa trên câu trả lời @Lasse? Thay vì điều vòng lặp?
Andre Miras

1
Hàng đợi không phải là phương tiện tốt nhất để thông báo lỗi, trừ khi bạn muốn có một hàng đợi đầy đủ. Một cấu trúc tốt hơn nhiều là
phân luồng.Event

1
Điều này có vẻ không an toàn với tôi. Điều gì xảy ra khi chủ đề tăng một ngoại lệ ngay sau khi bucket.get()tăng Queue.Empty? Sau đó, chủ đề join(0.1)sẽ hoàn thành và isAlive() is False, và bạn bỏ lỡ ngoại lệ của bạn.
Steve

1
Queuelà không cần thiết trong trường hợp đơn giản này - bạn chỉ có thể lưu trữ thông tin ngoại lệ dưới dạng một thuộc tính ExcThreadmiễn là bạn đảm bảo rằng nó run()hoàn thành ngay sau ngoại lệ (như trong ví dụ đơn giản này). Sau đó, bạn chỉ cần nâng lại ngoại lệ sau (hoặc trong) t.join(). Không có vấn đề đồng bộ hóa vì join()đảm bảo luồng đã hoàn thành. Xem câu trả lời của Rok Strniša bên dưới stackoverflow.com/a/12223550/126362
ejm

41

Các concurrent.futuresmô-đun làm cho nó đơn giản để làm việc trong chủ đề riêng biệt (hoặc quá trình) và xử lý bất kỳ trường hợp ngoại lệ kết quả:

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được bao gồm trong Python 3.2 và có sẵn dưới dạng mô đun backportedfutures cho các phiên bản trước.


5
Mặc dù điều này không thực hiện chính xác những gì OP yêu cầu, nhưng đây chính xác là gợi ý tôi cần. Cảm ơn bạn.
Nhà vật lý điên

2
Và với concurrent.futures.as_completed, bạn có thể nhận được thông báo ngay lập tức khi các ngoại lệ được nêu ra: stackoverflow.com/questions/2829329/
mẹo

1
Mã này đang chặn luồng chính. Làm thế nào để bạn làm điều này không đồng bộ?
Nikolay Shindarov

39

Có rất nhiều câu trả lời thực sự phức tạp đến kỳ lạ cho câu hỏi này. Tôi có quá đơn giản hóa điều này không, bởi vì điều này dường như đủ cho hầu hết mọi thứ đối với tôi.

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

Nếu bạn chắc chắn bạn sẽ chỉ chạy trên một hoặc phiên bản Python khác, bạn có thể giảm run()phương thức xuống chỉ còn phiên bản được xử lý (nếu bạn chỉ chạy trên các phiên bản Python trước 3), hoặc chỉ là phiên bản sạch (nếu bạn chỉ chạy trên các phiên bản Python bắt đầu bằng 3).

Ví dụ sử dụng:

def f(*args, **kwargs):
    print(args)
    print(kwargs)
    raise Exception('I suck at this')

t = PropagatingThread(target=f, args=(5,), kwargs={'hello':'world'})
t.start()
t.join()

Và bạn sẽ thấy ngoại lệ được nêu lên trên các chủ đề khác khi bạn tham gia.

Nếu bạn đang sử dụng sixhoặc chỉ trên Python 3, bạn có thể cải thiện thông tin theo dõi ngăn xếp bạn nhận được khi ngoại lệ được nêu lại. Thay vì chỉ ngăn xếp tại điểm nối, bạn có thể bọc ngoại lệ bên trong trong một ngoại lệ mới bên ngoài và nhận được cả dấu vết ngăn xếp với

six.raise_from(RuntimeError('Exception in thread'),self.exc)

hoặc là

raise RuntimeError('Exception in thread') from self.exc

1
Tôi không chắc tại sao câu trả lời này cũng không phổ biến hơn. Có những người khác ở đây cũng thực hiện việc truyền bá đơn giản, nhưng yêu cầu mở rộng một lớp và ghi đè. Điều này chỉ làm những gì nhiều người mong đợi và chỉ yêu cầu thay đổi từ Thread sang ProagatingThread. Và 4 tab không gian để sao chép / dán của tôi không đáng kể :-) ... cải tiến duy nhất tôi đề xuất là sử dụng six.raise_from () để bạn có được một tập hợp dấu vết ngăn xếp đẹp mắt, thay vì chỉ ngăn xếp cho trang web của sự phục hồi.
aggieNick02

Cảm ơn rât nhiều. Giải pháp rất đơn giản.
sonulohani

Vấn đề của tôi là tôi có nhiều chủ đề con. Các phép nối được thực hiện theo trình tự và ngoại lệ có thể được đưa ra từ các luồng được nối sau đó. Có một giải pháp đơn giản cho vấn đề của tôi? chạy tham gia đồng thời?
chuan

Cảm ơn, điều đó hoạt động hoàn hảo! Không chắc tại sao nó không được xử lý trực tiếp bởi python tho
GG.

Đây là definetelu câu trả lời hữu ích nhất, sự hờn dỗi này chung chung hơn nhiều so với những người khác nhưng đơn giản. Sẽ sử dụng nó trong một dự án!
Konstantin Sekeresh

30

Mặc dù không thể trực tiếp bắt một ngoại lệ được ném trong một luồng khác, nhưng đây là một mã để có được một thứ gì đó rất gần với chức năng này. Chủ đề con của bạn phải phân ExThreadlớp lớp thay vì threading.Threadvà chủ đề mẹ phải gọi child_thread.join_with_exception()phương thức thay vì child_thread.join()khi đợi chủ đề hoàn thành công việc của nó.

Chi tiết kỹ thuật của việc thực hiện này: khi luồng con ném một ngoại lệ, nó được truyền cho cha mẹ thông qua a Queuevà được ném lại trong luồng cha. Lưu ý rằng không có sự chờ đợi bận rộn trong phương pháp này.

#!/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 __name__ == '__main__':
    main()

1
Bạn có muốn bắt BaseExceptionkhông Exception? Tất cả những gì bạn đang làm là tuyên truyền ngoại lệ từ cái này Threadsang cái khác. Ngay bây giờ, IE, a KeyboardInterrupt, sẽ âm thầm bị bỏ qua nếu nó được nâng lên trong một luồng nền.
ArtOfWarfare 24/07/2015

join_with_exceptiontreo vô thời hạn nếu được gọi lần thứ hai trên một sợi chỉ chết. Khắc phục: github.com/fraserharris/threading-extensions/blob/master/ triệt
Fraser Harris

Tôi không nghĩ Queuelà cần thiết; xem bình luận của tôi để trả lời của @ Santa. Bạn có thể đơn giản hóa nó thành một câu như câu trả lời của Rok Strniša bên dưới stackoverflow.com/a/12223550/126362
ejm

22

Nếu một ngoại lệ xảy ra trong một luồng, cách tốt nhất là nâng lại nó trong luồng của người gọi trong suốt join. Bạn có thể nhận thông tin về ngoại lệ hiện đang được xử lý bằng cách sử dụng sys.exc_info()chức năng. Thông tin này có thể được lưu trữ đơn giản như một thuộc tính của đối tượng luồng cho đến khi joinđược gọi, tại thời điểm đó nó có thể được nâng lên.

Lưu ý rằng một Queue.Queue(như được đề xuất trong các câu trả lời khác) là không cần thiết trong trường hợp đơn giản này khi chủ đề ném tối đa 1 ngoại lệhoàn thành ngay sau khi ném ngoại lệ . Chúng tôi tránh các điều kiện cuộc đua bằng cách chờ đợi chuỗi hoàn thành.

Ví dụ: mở rộng ExcThread(bên dưới), ghi đè excRun(thay vì run).

Python 2.x:

import threading

class ExcThread(threading.Thread):
  def excRun(self):
    pass

  def run(self):
    self.exc = None
    try:
      # Possibly throws an exception
      self.excRun()
    except:
      import sys
      self.exc = sys.exc_info()
      # Save details of the exception thrown but don't rethrow,
      # just complete the function

  def join(self):
    threading.Thread.join(self)
    if self.exc:
      msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
      new_exc = Exception(msg)
      raise new_exc.__class__, new_exc, self.exc[2]

Python 3.x:

Biểu mẫu 3 đối số raiseđã biến mất trong Python 3, vì vậy hãy thay đổi dòng cuối cùng thành:

raise new_exc.with_traceback(self.exc[2])

2
Tại sao bạn sử dụng luồng.Thread.join (tự) thay vì siêu (ExcThread, tự) .join ()?
Richard Möhn

9

concurrent.futures.as_completed

https://docs.python.org/3.7/l Library / con hiện tại.futures.html # concản.futures.as_completed

Giải pháp sau:

  • trở về luồng chính ngay lập tức khi có ngoại lệ được gọi
  • không yêu cầu thêm các lớp do người dùng định nghĩa vì nó không cần:
    • một sự rõ ràng Queue
    • để thêm một ngoại trừ khác xung quanh chủ đề công việc của bạn

Nguồn:

#!/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()))

Sản lượng có thể:

0
0
1
1
2
2
0
Exception()
1
2
None

Thật không may, không thể giết chết tương lai để hủy bỏ những người khác vì một thất bại:

Nếu bạn làm một cái gì đó như:

for future in concurrent.futures.as_completed(futures):
    if future.exception() is not None:
        raise future.exception()

sau đó withbắt nó và đợi luồng thứ hai kết thúc trước khi tiếp tục. Các hành vi sau đây tương tự:

for future in concurrent.futures.as_completed(futures):
    future.result()

kể từ khi future.result()tăng lại ngoại lệ nếu xảy ra.

Nếu bạn muốn thoát khỏi toàn bộ quá trình Python, bạn có thể thoát khỏi os._exit(0), nhưng điều này có thể có nghĩa là bạn cần một bộ tái cấu trúc.

Lớp tùy chỉnh với ngữ nghĩa ngoại lệ hoàn hảo

Cuối cùng tôi đã mã hóa giao diện hoàn hảo cho mình tại: Cách đúng để hạn chế số lượng luồng tối đa chạy cùng một lúc? phần "Ví dụ xếp hàng với xử lý lỗi". Lớp đó nhằm mục đích vừa thuận tiện, vừa cung cấp cho bạn toàn quyền kiểm soát việc gửi và xử lý kết quả / lỗi.

Đã thử nghiệm trên Python 3.6.7, Ubuntu 18.04.


4

Đây là một vấn đề nhỏ khó chịu và tôi muốn đưa giải pháp của mình vào. Một số giải pháp khác mà tôi tìm thấy (ví dụ như async.io) có vẻ đầy hứa hẹn nhưng cũng có một chút hộp đen. Cách tiếp cận vòng lặp hàng đợi / sự kiện sắp xếp bạn vào một triển khai nhất định. Tuy nhiên, mã nguồn tương lai đồng thời chỉ có khoảng 1000 dòng và dễ hiểu . Nó cho phép tôi dễ dàng giải quyết vấn đề của mình: tạo các luồng công nhân đặc biệt mà không cần thiết lập nhiều và có thể bắt ngoại lệ trong luồng chính.

Giải pháp của tôi sử dụng API tương lai đồng thời và API luồng. Nó cho phép bạn tạo một worker cung cấp cho bạn cả chuỗi và tương lai. Bằng cách đó, bạn có thể tham gia chuỗi để chờ kết quả:

worker = Worker(test)
thread = worker.start()
thread.join()
print(worker.future.result())

... Hoặc bạn có thể để nhân viên chỉ gửi lại cuộc gọi khi hoàn tất:

worker = Worker(test)
thread = worker.start(lambda x: print('callback', x))

... hoặc bạn có thể lặp cho đến khi sự kiện hoàn thành:

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)

Đây là mã:

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())

... và chức năng kiểm tra:

def test(*args):
    print('args are', args)
    time.sleep(2)
    raise Exception('foo')

2

Là một người không quen với Threading, tôi phải mất một thời gian dài để hiểu cách triển khai mã của Mateusz Kobos (ở trên). Đây là một phiên bản được làm rõ để giúp hiểu cách sử dụng nó.

#!/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 __name__ == '__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

2

Cách tương tự như của RickardSjogren không có Queue, sys, v.v. nhưng cũng không có một số người nghe tín hiệu: thực hiện trực tiếp một trình xử lý ngoại lệ tương ứng với một khối ngoại trừ.

#!/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

Chỉ self._callback và khối ngoại trừ trong run () là bổ sung cho luồng thông thường.Thread.


2

Tôi biết tôi đến bữa tiệc muộn một chút nhưng tôi gặp vấn đề tương tự nhưng nó bao gồm sử dụng tkinter làm GUI và mainloop khiến không thể sử dụng bất kỳ giải pháp nào phụ thuộc vào .join (). Do đó, tôi đã điều chỉnh giải pháp được đưa ra trong EDIT của câu hỏi ban đầu, nhưng làm cho nó chung chung hơn để dễ hiểu hơn cho người khác.

Đây là lớp chủ đề mới đang hoạt động:

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 __name__ == "__main__":
    input = 'useful'

    t1 = ExceptionThread(target=test_function_1, args=[input])
    t1.start()

Tất nhiên, bạn luôn có thể yêu cầu nó xử lý ngoại lệ theo một cách khác từ việc đăng nhập, chẳng hạn như in ra hoặc đưa nó ra bàn điều khiển.

Điều này cho phép bạn sử dụng lớp ExceptionThread giống hệt như lớp Thread, mà không cần sửa đổi gì đặc biệt.


1

Một phương pháp tôi thích là dựa trên mẫu người quan sát . Tôi định nghĩa một lớp tín hiệu mà luồng của tôi sử dụng để phát ra ngoại lệ cho người nghe. Nó cũng có thể được sử dụng để trả về giá trị từ các luồng. Thí dụ:

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 __name__ == '__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'

Tôi không có đủ kinh nghiệm làm việc với các chủ đề để khẳng định rằng đây là một phương pháp hoàn toàn an toàn. Nhưng nó đã làm việc cho tôi và tôi thích sự linh hoạt.


bạn có ngắt kết nối sau khi tham gia () không?
Phục sinh

Tôi không, nhưng tôi đoán đó sẽ là một ý tưởng hay vì vậy bạn không có tài liệu tham khảo về những thứ không được sử dụng.
RickardSjogren

tôi nhận thấy rằng "handle_exception" vẫn là một phần của chuỗi con. cần phải chuyển nó đến người gọi chủ đề
ealeon

1

Sử dụng ngoại lệ trần trụi không phải là một thực hành tốt bởi vì bạn thường bắt được nhiều hơn bạn mặc cả.

Tôi sẽ đề nghị sửa đổi exceptđể bắt CHỈ ngoại lệ mà bạn muốn xử lý. Tôi không nghĩ rằng việc nâng cao nó có hiệu quả như mong muốn, bởi vì khi bạn bắt đầu khởi tạo TheThreadở bên ngoài try, nếu nó phát sinh một ngoại lệ, việc chuyển nhượng sẽ không bao giờ xảy ra.

Thay vào đó, bạn có thể muốn cảnh báo về nó và tiếp tục, chẳng hạn như:

def run(self):
    try:
       shul.copytree(self.sourceFolder, self.destFolder)
    except OSError, err:
       print err

Sau đó, khi ngoại lệ đó bị bắt, bạn có thể xử lý nó ở đó. Sau đó, khi bên ngoài trybắt được một ngoại lệ từ đó TheThread, bạn biết đó sẽ không phải là cái bạn đã xử lý và sẽ giúp bạn cách ly dòng quy trình của mình.


1
Chà, nếu có lỗi trong luồng đó, tôi muốn chương trình đầy đủ thông báo cho người dùng rằng có một vấn đề và kết thúc một cách duyên dáng. Vì lý do đó, tôi muốn luồng chính bắt và xử lý tất cả các ngoại lệ. Tuy nhiên, vấn đề vẫn tồn tại khi TheThread ném ngoại lệ, thử / ngoại trừ chủ đề chính vẫn không bắt được. Tôi có thể có chủ đề phát hiện ngoại lệ và trả về một sai cho biết rằng hoạt động không thành công. Điều đó sẽ đạt được kết quả mong muốn tương tự, nhưng tôi vẫn muốn biết làm thế nào để bắt đúng một ngoại lệ tiểu luồng.
Phanto

1

Một cách đơn giản để bắt ngoại lệ của luồng và truyền lại cho phương thức người gọi có thể bằng cách chuyển từ điển hoặc danh sách sang workerphương thức.

Ví dụ (truyền từ điển cho phương thức 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'])

1

Gói Thread với lưu trữ ngoại lệ.

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()

0

pygolang cung cấp sync.Workgroup , đặc biệt, truyền ngoại lệ từ các luồng công nhân được sinh ra đến luồng chính. Ví dụ:

#!/usr/bin/env python
"""This program demostrates how with sync.WorkGroup an exception raised in
spawned thread is propagated into main thread which spawned the worker."""

from __future__ import print_function
from golang import sync, context

def T1(ctx, *argv):
    print('T1: run ... %r' % (argv,))
    raise RuntimeError('T1: problem')

def T2(ctx):
    print('T2: ran ok')

def main():
    wg = sync.WorkGroup(context.background())
    wg.go(T1, [1,2,3])
    wg.go(T2)

    try:
        wg.wait()
    except Exception as e:
        print('Tmain: caught exception: %r\n' %e)
        # reraising to see full traceback
        raise

if __name__ == '__main__':
    main()

đưa ra những điều sau đây khi chạy:

T1: run ... ([1, 2, 3],)
T2: ran ok
Tmain: caught exception: RuntimeError('T1: problem',)

Traceback (most recent call last):
  File "./x.py", line 28, in <module>
    main()
  File "./x.py", line 21, in main
    wg.wait()
  File "golang/_sync.pyx", line 198, in golang._sync.PyWorkGroup.wait
    pyerr_reraise(pyerr)
  File "golang/_sync.pyx", line 178, in golang._sync.PyWorkGroup.go.pyrunf
    f(pywg._pyctx, *argv, **kw)
  File "./x.py", line 10, in T1
    raise RuntimeError('T1: problem')
RuntimeError: T1: problem

Mã ban đầu từ câu hỏi sẽ chỉ là:

    wg = sync.WorkGroup(context.background())

    def _(ctx):
        shul.copytree(sourceFolder, destFolder)
    wg.go(_)

    # waits for spawned worker to complete and, on error, reraises
    # its exception on the main thread.
    wg.wait()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.