1# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import threading 16from typing import Optional 17 18 19class StoppableThread(object): 20 """ 21 Provide a Thread-like class which can be 'cancelled' via a subclass-provided 22 cancellation method. 23 24 Can be started and stopped multiple times. 25 26 Isn't an instance of type Thread because Python Thread objects can only be run once 27 """ 28 29 def __init__(self): 30 # type: () -> None 31 self._thread = None # type: Optional[threading.Thread] 32 33 @property 34 def alive(self): 35 # type: () -> bool 36 """ 37 Is 'alive' whenever the internal thread object exists 38 """ 39 return self._thread is not None 40 41 def start(self): 42 # type: () -> None 43 if self._thread is None: 44 self._thread = threading.Thread(target=self._run_outer) 45 self._thread.start() 46 47 def _cancel(self): 48 # type: () -> None 49 pass # override to provide cancellation functionality 50 51 def run(self): 52 # type: () -> None 53 pass # override for the main thread behaviour 54 55 def _run_outer(self): 56 # type: () -> None 57 try: 58 self.run() 59 finally: 60 self._thread = None 61 62 def stop(self): 63 # type: () -> None 64 if self._thread is not None: 65 old_thread = self._thread 66 self._thread = None 67 self._cancel() 68 old_thread.join() 69