如何在单独的线程中运行上下文管理器下的代码?

问题描述 投票:0回答:1

我想要一个上下文管理器,我可以在其中放置一些要在单独的线程中执行的代码。

到目前为止,我找不到一种方法来实现我想要的,最好的选择是编写闭包并在单独的线程中执行闭包。

我想要这样的东西

# code runs on main thread
print("this is main thread")

with amazingcontextmanager:
    # code to run in separate thread
    print("this is not main thread")

编辑:让我尝试再次问我的问题

@contextlib.contextmanager
def amazingcontextmanager():
    try:
        yield
    finally:
        print("thread done")

我希望

yield
在新线程中执行。基本上我放在 contextmanager 下的任何内容都应该在单独的线程中执行。

python python-3.x multithreading python-multithreading
1个回答
0
投票

虽然与问题 Is it possible to access the context object (code block) inside the

__exit__()
method of a context manager? 在识别
with
块的代码方面类似,但该问题的不同之处在于上下文中的代码无法直接执行,因为您希望它在单独的线程中执行,因此您需要一种方法来阻止在上下文管理器的
with
方法返回后执行
__enter__
块。

绕过

with
块主体的执行并直接跳转到上下文管理器的
__exit__
方法的一种方法是引发异常。但是在
__enter__
方法中引发异常会导致上下文管理器之外发生彻底的异常,而无需调用我们想要启动线程的
__exit__
方法。因此,我们可以在
__enter__
方法返回后引发异常,方法是在为
sys.settrace
和调用者框架设置的跟踪函数中执行此操作:

import sys
import threading
from linecache import getline
from tokenize import tokenize, INDENT, DEDENT

class ThreadContext:
    class EndContext(Exception):
        pass

    def _skip_execution(self, frame, event, arg):
        raise self.EndContext

    def __enter__(self):
        def readline():
            lineno = caller.f_lineno
            while line := getline(filename, lineno):
                if lineno == caller.f_lineno:  # dedent the with statement
                    line = line.lstrip()       # so it can be parsed alone
                yield line.encode()
                lineno += 1
            yield b''

        caller = sys._getframe(1)
        filename = caller.f_code.co_filename
        first = end = depth = 0
        try:
            for token, _, (start, _), (end, _), _ in tokenize(readline().__next__):
                if token == INDENT:
                    depth += 1
                    if not first:
                        first = start
                elif token == DEDENT:
                    if depth == 1:
                        break
                    depth -= 1
        except IndentationError:
            end += 1
        body = ''.join(
            getline(filename, caller.f_lineno + lineno - 1)
            for lineno in range(first, end)
        )
        self.namespace = {}
        self.thread = threading.Thread(
            target=exec,
            args=(
                compile('if 1:\n' + body, '\n' + body, 'exec'),
                caller.f_globals,
                self.namespace
            )
        )
        self.tracer = threading.gettrace()
        caller.f_trace = self._skip_execution
        sys.settrace(self._skip_execution)
        return self.thread

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is self.EndContext:
            caller = sys._getframe(1)
            caller.f_trace = self.tracer
            sys.settrace(self.tracer) # restore the original trace function
            self.namespace.update(caller.f_locals)
            self.thread.start()
            return True

这样:

from time import sleep

def main():
    foo = []
    with ThreadContext() as thread:
        for _ in range(3):
            sleep(.9)
            print(f'sleeping in {thread}')
        foo.append(1)
    while not foo:
        print('foo is empty')
        sleep(1)
    print('foo got', foo.pop())
    thread.join()

main()

输出:

foo is empty
sleeping in <Thread(Thread-1 (exec), started 139934645712576)>
foo is empty
sleeping in <Thread(Thread-1 (exec), started 139934645712576)>
foo is empty
sleeping in <Thread(Thread-1 (exec), started 139934645712576)>
foo got 1

演示:https://replit.com/@blhsing1/DetailedDarlingSoftwareagent

© www.soinside.com 2019 - 2024. All rights reserved.