New GitHub issue #101344 from amdshrif:<br>
<hr>
<pre>
# Feature or enhancement
Add `add_running_callback()` and `remove_running_callback()` methods to `concurrent.futures.Future` and `asyncio.Future`
# Pitch
The `concurrent.futures.Future` and `asyncio.Future` module in Python provide a convenient way to run parallel and asynchronous tasks. However, they currently lacks the ability to add and remove callbacks that are executed when a Future is running. This proposal aims to add two new methods to the Future class, `add_running_callback()` and `remove_running_callback()`, which will allow developers to add and remove callbacks that are executed when a Future is running. This will provide more flexibility and control over the execution of parallel and asynchronous tasks.
### Example usage:
Here is an example of how the new methods will be used:
```python
import concurrent.futures
import time
def some_long_running_task():
print('Running a long running task')
time.sleep(10)
return 42
def running_callback_1(future):
print(f'{future} is running')
def running_callback_2(future):
print(f'{future} is still running')
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(some_long_running_task)
future.add_running_callback(running_callback_1)
future.add_running_callback(running_callback_2)
# do something else here like ping a server
future.remove_running_callback(running_callback_1)
# do something else here like fetch some data
future.remove_running_callback(running_callback_1)
# do something else here
```
In the above example, `running_callback_1` and `running_callback_2` are added and then removed from the list of running callbacks for the Future. Here is how to achieve the same but in blocking manner:
```python
import concurrent.futures
import time
def some_long_running_task():
print('Running a long running task')
time.sleep(10)
return 42
def running_callback(future):
print(f'{future} is running')
print(f'ping or fetch something while {future} is running')
print(f'check something else while {future} is running')
def done_callback(future):
print(f'{future} is done and returned {future.result()}')
def while_running(future):
while not future.done():
print('Running a long running task')
time.sleep(1)
def main():
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(some_long_running_task)
future.add_done_callback(done_callback)
while_running(future)
if __name__ == '__main__':
main()
```
# Previous discussion
This feature has not been previously discussed.
# Design and Implementation
The proposed `add_running_callback()` and `remove_running_callback()` methods will be added to the Future class in both the `concurrent.futures.Future` and `asyncio.Future`. The `add_running_callback()` method will take a single argument, a callable object, and will add it to a list of callbacks to be executed when the Future is running. The `remove_running_callback()` method will also take a single argument, a callable object, and will remove it from the list of callbacks.
The `add_running_callback()` and `remove_running_callback()` methods will be implemented by adding a new attribute, `_running_callbacks`, to the Future class. This attribute will be a list of callbacks that are executed when the Future is running. The `add_running_callback()` method will simply append the passed callback to the list, and the `remove_running_callback()` method will remove it.
In addition, the `Future.init` method will be updated to initialize the `_running_callbacks` attribute as an empty list. And the `Future._invoke_callbacks` method will be updated to invoke the callbacks in the `_running_callbacks` list, in addition to the done callbacks, when the Future's state changes.
# Backwards compatibility
The proposed changes to the Future class will not affect any existing code that uses both the `concurrent.futures.Future` and `asyncio.Future`. The new methods, `add_running_callback()` and `remove_running_callback()`, are optional and will not be used by any code that does not specifically call them.
# Conclusion
Adding the `add_running_callback()` and `remove_running_callback()` methods to the Future class in both the `concurrent.futures.Future` and `asyncio.Future` will provide greater flexibility and control over the execution of parallel and asynchronous tasks. This proposal is backwards-compatible and provides a simple and straightforward solution to a common problem faced by developers using both the `concurrent.futures.Future` and `asyncio.Future`.
P.S. This would be my first contribution and I have already had quick look at Python Developer’s Guide and PEP Purpose and Guidelines, and I believe this kind of proposals falls under enhancements effort but if I missed something or this is not the right place for this proposal, please kindly let me know.
</pre>
<hr>
<a href="https://github.com/python/cpython/issues/101344">View on GitHub</a>
<p>Labels: type-feature</p>
<p>Assignee: </p>