How to stop function from finishing its code in event loop - python

I have event loop that runs the function asynchronously. However, that function generates big data so the execution will be a little bit long when the program visits that function. I also implemented a stop button, so the app will exit that function even if the event loop is not yet finish. The problem is that is how to exit on that function immediately or how to kill a thread in asyncio.
I already tried using a flag in the function but the execution of the function is fast enough to check the flag before the user click on the stop button. To be short, the thread already running on the background.
def execute_function(self, function_to_execute, *args):
self.loop = asyncio.get_event_loop()
self.future = self.loop.run_in_executor(self._executor, function_to_execute, *args)
return self.future
def stop_function(self):
if self._executor:
self._executor.shutdown(wait=False)
self.loop.stop()
self.loop.close()
Is there something wrong or missing on the code I've given? The expected output should be, the program will not generate the data at the end if I click the stop button.

You can use threading.Event passing it to your blocking function
event = threading.Event()
future = loop.run_in_executor(executor, blocking, event)
# When done
event.set()
The blocking function just has to check is_set and when you want to stop it just call event.set() as above.
def blocking(event):
while 1:
time.sleep(1)
if event.is_set():
break

Related

Difference between asyncio call_soon and call_soon_threadsafe? Why is call_soon thread-unsafe?

Recently I read the code of asyncio and felt confused about loop.call_soon and loop.call_soon_threadsafe. The only difference I found is that there is a _write_to_self in call_soon_threadsafe. As we know, a event loop runs in a specific thread. All tasks scheduled by the event loop orderly. And we can assume tasks are thread safe while they were scheduled one by one. So how can thread unsafe situation happen in call_soon?
Briefly: The call_soon_threadsafe method will allow a non-event loop thread to run a callable in the event loop thread. This does the following:
If the event loop thread is waiting, call_soon_threadsafe wakes the event loop thread to process the callable. Without this, per the example below, the event loop thread will wait indefinitely and not process the callable.
After waking the event loop, the event loop calls the callable in the context of the event loop thread. The callable is therefore run in the context of the event loop thread and can safely access any objects of the event loop thread as though they were accessed from the event loop code. In the failing case example below, we can see an "unsafe" event set is not observed by the event loop because the loop is not awakened.
The example below is relatively trivial but keep in mind what the docs mention... from Concurrency and Multithreading...
"... Almost all asyncio objects are not thread safe, which is
typically not a problem unless there is code that works with them from
outside of a Task or a callback. ..."
This means, if you have a non-event loop thread that wishes to touch objects that should not be touched outside of the event loop thread, you should run the callable in the context of the event loop thread by using call_soon_threadsafe.
Yes, both call_soon_threadsafe and call_soon queue the work, but the call_soon_threadsafe variation will wake the event loop thread to process the callable in its thread. Without waking, the work would not be performed if the event loop thread is waiting (per the example if SHOW_MAIN_HANG=True).
Example demonstrating safe/unsafe difference...
Set SHOW_MAIN_HANG=True to see one example issue. See how SHOW_MAIN_HANG=False uses the event loop thread callback to properly set the event and wake the event loop thread to observe the signal.
import asyncio
import threading
import time
# Set SHOW_MAIN_HANG to True to see main loop hang due to unsafe event set.
# Set SHOW_MAIN_HANG to False to see main loop properly exit due to save event set.
# The term "safe" here means the main loop is awaked to process any work
# adjusted by non-asyncio threads such as in the example below.
# Most importantly, "safe" here means the callable is run in the context
# of the event loop (main loop below) thread, which means it can access any
# event loop items that woudl be unsafe to access from a different thread.
SHOW_MAIN_HANG = False
def print_thread_info_msg(msg):
print(
f"{msg}: "
f"name={threading.current_thread().name} "
f"native_id={threading.current_thread().native_id}"
)
def set_the_event(event: asyncio.Event):
print_thread_info_msg("Setting the event from callable")
event.set()
def other_thread_func(
main_loop: asyncio.AbstractEventLoop,
main_loop_event: asyncio.Event
):
# For example, this thread is doing other work.
# This thread's work is being performed while the
# main loop is both running tasks but also when the
# main loop might be doing nothing, waiting. This
# thread cannot predict if the main loop is running
# or waiting, nor should it have to conern itself
# with such details. Nevertheless, this thread wishes
# to signal the main loop when something happens, perhaps
# when there's work ready, or maybe to indicate the
# work is completed, the program should end. There
# many examples to choose from... this is just one
# abstract hypothetical.
print_thread_info_msg("other_thread_func: doing some other work")
time.sleep(3)
if SHOW_MAIN_HANG:
# This way sets the event but does not wake the main loop
# which means the event can be set but the main loop sits
# waiting, not checking the vent. This will cause main loop
# to hang, not knowing the event has been signaled.
print(f"other_thread_func: set (unsafe way)")
main_loop_event.set()
else:
# This way sets the event and wakes the main loop, which
# avoids the hang.
print(f"other_thread_func: set (safe way)")
main_loop.call_soon_threadsafe(set_the_event, main_loop_event)
async def main():
print_thread_info_msg("Main event loop thread")
main_loop = asyncio.get_running_loop()
main_loop_event = asyncio.Event()
thd = threading.Thread(target=other_thread_func, args=(main_loop, main_loop_event,))
thd.start()
print(f"main: wait")
the_task = asyncio.create_task(main_loop_event.wait())
await asyncio.wait([the_task])
print(f"main: wait completed")
asyncio.run(main())
print("program exit")
Example with SHOW_MAIN_HANG=False...
Main event loop thread: name=MainThread native_id=2924
main: wait
other_thread_func: doing some other work: name=Thread-7 (other_thread_func) native_id=8156
other_thread_func: set (safe way)
Setting the event from callable: name=MainThread native_id=2924
main: wait completed
program exit
Example with SHOW_MAIN_HANG=True...
Main event loop thread: name=MainThread native_id=19240
main: wait
other_thread_func: doing some other work: name=Thread-7 (other_thread_func) native_id=21088
other_thread_func: set (unsafe way)
(hangs here, does not exit)
As you observed, a primary difference is the safe variation calls _write_to_self. The _write_to_self method will wake the event loop thread. If that did not happen, the work could be queued but not processed by the event loop thread as can be seen in the example above when SHOW_MAIN_HANG=True.
From CPython ...\Lib\asyncio\base_events.py
...
def call_soon_threadsafe(self, callback, *args, context=None):
"""Like call_soon(), but thread-safe."""
self._check_closed()
if self._debug:
self._check_callback(callback, 'call_soon_threadsafe')
handle = self._call_soon(callback, args, context)
if handle._source_traceback:
del handle._source_traceback[-1]
self._write_to_self()
return handle
...
You're right: if we have only one event loop running in a single thread (which is often the case), we don't need call_soon_threadsafe.
But sometimes people want to run an event loop in one thread, but call soon from another (for whatever reason). In this case you'll need call_soon_threadsafe.
Take a look at the question here and the answer.

How to stop loop running in executor?

I am running function that takes time to finish. The user has a choice to stop this function/event. Is there an easy way to stop the thread or loop?
class ThreadsGenerator:
MAX_WORKERS = 5
def __init__(self):
self._executor = ThreadPoolExecutor(max_workers=self.MAX_WORKERS)
self.loop = None
self.future = None
def execute_function(self, function_to_execute, *args):
self.loop = asyncio.get_event_loop()
self.future = self.loop.run_in_executor(self._executor, function_to_execute, *args)
return self.future
I want to stop the function as quickly as possible when the user click the stop button, not waiting to finish its job.
Thanks in advance!
Is there an easy way to stop the thread or loop?
You cannot forcefully stop a thread. To implement the cancel functionality, your function will need to accept a should_stop argument, for example an instance of threading.Event, and occasionally check if it has been set.
If you really need a forceful stop, and if your function is runnable in a separate process through multiprocessing, you can run it in a separate process and kill the process when it is supposed to stop. See this answer for an elaboration of that approach in the context of asyncio.

How to start a thread again with an Event object in Python?

I want to make a thread and control it with an event object. Detailedly speaking, I want the thread to be executed whenever the event object is set and to wait itselt, repeatedly.
The below shows a sketchy logic I thought of.
import threading
import time
e = threading.Event()
def start_operation():
e.wait()
while e.is_set():
print('STARTING TASK')
e.clear()
t1 = threading.Thread(target=start_operation)
t1.start()
e.set() # first set
e.set() # second set
I expected t1 to run once the first set has been commanded and to stop itself(due to e.clear inside it), and then to run again after the second set has been commanded. So, accordign to what I expected, it should print out 'STARTING TASK' two times. But it shows it only once, which I don't understand why. How am I supposed to change the code to make it run the while loop again, whenever the event object is set?
The first problem is that once you exit a while loop, you've exited it. Changing the predicate back won't change anything. Forget about events for a second and just look at this code:
i = 0
while i == 0:
i = 1
It obviously doesn't matter if you set i = 0 again later, right? You've already left the while loop, and the whole function. And your code is doing exactly the same thing.
You can fix problem that by just adding another while loop around the whole thing:
def start_operation():
while True:
e.wait()
while e.is_set():
print('STARTING TASK')
e.clear()
However, that still isn't going to work—except maybe occasionally, by accident.
Event.set doesn't block; it just sets the event immediately, even if it's already set. So, the most likely flow of control here is:
background thread hits e.wait() and blocks.
main thread hits e.set() and sets event.
main thread hits e.set() and sets event again, with no effect.
background thread wakes up, does the loop once, calls e.clear() at the end.
background thread waits forever on e.wait().
(The fact that there's no way to avoid missed signals with events is effectively the reason conditions were invented, and that anything newer than Win32 and Python doesn't bother with events… But a condition isn't sufficient here either.)
If you want the main thread to block until the event is clear, and only then set it again, you can't do that. You need something extra, like a second event, which the main thread can wait on and the background thread can set.
But if you want to keep track of multiple set calls, without missing any, you need to use a different sync mechanism. A queue.Queue may be overkill here, but it's dead simple to do in Python, so let's just use that. Of course you don't actually have any values to put on the queue, but that's OK; you can just stick a dummy value there:
import queue
import threading
q = queue.Queue()
def start_operation():
while True:
_ = q.get()
print('STARTING TASK')
t1 = threading.Thread(target=start_operation)
t1.start()
q.put(None)
q.put(None)
And if you later want to add a way to shut down the background thread, just change it to stick values on:
import queue
import threading
q = queue.Queue()
def start_operation():
while True:
if q.get():
return
print('STARTING TASK')
t1 = threading.Thread(target=start_operation)
t1.start()
q.put(False)
q.put(False)
q.put(True)

Does asyncio.wait return only after all done_callbacks were called?

Imagine the following code:
import asyncio
loop = asyncio.get_event_loop()
#asyncio.coroutine
def coro():
yield from asyncio.sleep(1)
def done_callback(future):
print("Callback called")
#asyncio.coroutine
def run():
future = asyncio.async(coro(), loop=loop)
future.add_done_callback(done_callback)
yield from asyncio.wait([future])
print("Wait returned")
loop.run_until_complete(run())
Output is:
$ python3 /tmp/d.py
Callback called
Wait returned
So done_callback was called before wait returned.
Is this guaranteed behavior? I did not find anything in documentation about this.
Is this possible situation when done_callback called after wait returned?
With the current asyncio implementation, as long as add_done_callback is called before the event loop iteration that coro actually completes, all the callback scheduled with add_done_callback will execute before wait unblocks. The reason is that asyncio.wait internally calls add_done_callback on all the Future instances you pass to it, so it's just another callback in the callback chain for the Task. When your Task completes, asyncio calls set_result on it, which looks like this:
def set_result(self, result):
"""Mark the future done and set its result.
If the future is already done when this method is called, raises
InvalidStateError.
"""
if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self))
self._result = result
self._state = _FINISHED
self._schedule_callbacks()
_schedule_callbacks looks like this:
def _schedule_callbacks(self):
"""Internal: Ask the event loop to call all callbacks.
The callbacks are scheduled to be called as soon as possible. Also
clears the callback list.
"""
callbacks = self._callbacks[:]
if not callbacks:
return
self._callbacks[:] = []
for callback in callbacks:
self._loop.call_soon(callback, self)
So, once the Task is done, loop.call_soon is used to schedule all the callbacks (which includes your done_callback function, and the callback added by asyncio.wait).
The event loop will process all the callbacks in the internal callback list in one iteration, which means both the asyncio.wait callback and your done_callback will be executed together in a single event loop iteration:
# This is the only place where callbacks are actually *called*.
# All other places just add them to ready.
# Note: We run all currently scheduled callbacks, but not any
# callbacks scheduled by callbacks run this time around --
# they will be run the next time (after another I/O poll).
# Use an idiom that is thread-safe without using locks.
ntodo = len(self._ready)
for i in range(ntodo):
handle = self._ready.popleft()
if handle._cancelled:
continue
if self._debug:
t0 = self.time()
handle._run()
So, as long as your add_done_callback ran prior to the event loop iteration where coro completed, you're guaranteed (at least by the current implementation) that it will run before asyncio.wait unblocks. However, if add_done_callback is executed either after coro completes or on the same event loop iteration that coro completes, it won't run until after asyncio.wait finishes.
I would say that if add_done_callback is called before asyncio.wait, like in your example, you can be confident it will always run before wait unblocks, since your callback will be ahead of the asyncio.wait callback in the callback chain. If you end up calling add_done_callback after asyncio.wait is started, it will still work for now, but theoretically the implementation could change in a way that would make it not; it could be changed to only process a limited number of callbacks on every event loop iteration, for example. I doubt that change will ever be made, but it's possible.

Python Queue waiting for thread before getting next item

I have a queue that always needs to be ready to process items when they are added to it. The function that runs on each item in the queue creates and starts thread to execute the operation in the background so the program can go do other things.
However, the function I am calling on each item in the queue simply starts the thread and then completes execution, regardless of whether or not the thread it started completed. Because of this, the loop will move on to the next item in the queue before the program is done processing the last item.
Here is code to better demonstrate what I am trying to do:
queue = Queue.Queue()
t = threading.Thread(target=worker)
t.start()
def addTask():
queue.put(SomeObject())
def worker():
while True:
try:
# If an item is put onto the queue, immediately execute it (unless
# an item on the queue is still being processed, in which case wait
# for it to complete before moving on to the next item in the queue)
item = queue.get()
runTests(item)
# I want to wait for 'runTests' to complete before moving past this point
except Queue.Empty, err:
# If the queue is empty, just keep running the loop until something
# is put on top of it.
pass
def runTests(args):
op_thread = SomeThread(args)
op_thread.start()
# My problem is once this last line 't.start()' starts the thread,
# the 'runTests' function completes operation, but the operation executed
# by some thread is not yet done executing because it is still running in
# the background. I do not want the 'runTests' function to actually complete
# execution until the operation in thread t is done executing.
"""t.join()"""
# I tried putting this line after 't.start()', but that did not solve anything.
# I have commented it out because it is not necessary to demonstrate what
# I am trying to do, but I just wanted to show that I tried it.
Some notes:
This is all running in a PyGTK application. Once the 'SomeThread' operation is complete, it sends a callback to the GUI to display the results of the operation.
I do not know how much this affects the issue I am having, but I thought it might be important.
A fundamental issue with Python threads is that you can't just kill them - they have to agree to die.
What you should do is:
Implement the thread as a class
Add a threading.Event member which the join method clears and the thread's main loop occasionally checks. If it sees it's cleared, it returns. For this override threading.Thread.join to check the event and then call Thread.join on itself
To allow (2), make the read from Queue block with some small timeout. This way your thread's "response time" to the kill request will be the timeout, and OTOH no CPU choking is done
Here's some code from a socket client thread I have that has the same issue with blocking on a queue:
class SocketClientThread(threading.Thread):
""" Implements the threading.Thread interface (start, join, etc.) and
can be controlled via the cmd_q Queue attribute. Replies are placed in
the reply_q Queue attribute.
"""
def __init__(self, cmd_q=Queue.Queue(), reply_q=Queue.Queue()):
super(SocketClientThread, self).__init__()
self.cmd_q = cmd_q
self.reply_q = reply_q
self.alive = threading.Event()
self.alive.set()
self.socket = None
self.handlers = {
ClientCommand.CONNECT: self._handle_CONNECT,
ClientCommand.CLOSE: self._handle_CLOSE,
ClientCommand.SEND: self._handle_SEND,
ClientCommand.RECEIVE: self._handle_RECEIVE,
}
def run(self):
while self.alive.isSet():
try:
# Queue.get with timeout to allow checking self.alive
cmd = self.cmd_q.get(True, 0.1)
self.handlers[cmd.type](cmd)
except Queue.Empty as e:
continue
def join(self, timeout=None):
self.alive.clear()
threading.Thread.join(self, timeout)
Note self.alive and the loop in run.

Categories