43

I am creating a flask application, for one request I need to run some long running job which is not required to wait on the UI. I will create a thread and send a message to UI. The thread will calculate and update the database. But, UI will see a message upon submit. Below is my implementation, but it is running the thread and then sending the output to UI which is not I prefer. How can I run this thread in the background?

@app.route('/someJob')
def index():
    t1 = threading.Thread(target=long_running_job)
    t1.start()
    return 'Scheduled a job'

def long_running_job
    #some long running processing here

How can I make thread t1 to run the background and immediately send message in return?

foxyblue
  • 2,859
  • 2
  • 21
  • 29
San
  • 726
  • 2
  • 9
  • 18

6 Answers6

104

Try this example, tested on Python 3.4.3 / Flask 0.11.1

from flask import Flask
from time import sleep
from concurrent.futures import ThreadPoolExecutor

# DOCS https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor
executor = ThreadPoolExecutor(2)

app = Flask(__name__)


@app.route('/jobs')
def run_jobs():
    executor.submit(some_long_task1)
    executor.submit(some_long_task2, 'hello', 123)
    return 'Two jobs were launched in background!'


def some_long_task1():
    print("Task #1 started!")
    sleep(10)
    print("Task #1 is done!")


def some_long_task2(arg1, arg2):
    print("Task #2 started with args: %s %s!" % (arg1, arg2))
    sleep(5)
    print("Task #2 is done!")


if __name__ == '__main__':
    app.run()
c24b
  • 5,278
  • 6
  • 27
  • 35
Denys Synashko
  • 1,804
  • 3
  • 14
  • 13
  • Yes this works in python3, but my question was specific to 2.7 – San Aug 16 '17 at 12:59
  • 1
    are you able to run this using any web-server except localhost, I mean gunicorn or apache-wsgi – arshpreet Oct 03 '17 at 12:51
  • 3
    @arshpreet I have almost the same code working in production, running via uWSGI from 2016 until now without any problems. Test with Gunicorn also works just fine. – Denys Synashko Oct 04 '17 at 21:40
  • @arshpreet Here are the relevant lines from the real code of the project - https://gist.github.com/itnow/af696c41d9e61a4aea5baf0e1098e305 – Denys Synashko Oct 04 '17 at 21:47
  • can you tell me what is TASK_RUN_THREADPOOL?(in the above gist) – arshpreet Oct 23 '17 at 07:23
  • @arshpreet It's just a constant with integer number - set max workers you want to be available to executor. – Denys Synashko Oct 24 '17 at 09:27
  • 2
    If you want to use concurrent.futures with Flask, check out [Flask-Executor](https://github.com/dchevell/flask-executor). It provides a more idiomatic way of initialising an executor in Flask and provides some handy features (retaining the app context, application factory pattern support, decorators) – daveruinseverything Aug 28 '18 at 01:23
  • @DenysSynashko Is it possible to release ThreadPoolExecutor after a long-time task finished? – CoderYel Aug 06 '19 at 10:38
  • why you have to put executor inside an route? what if i want to run it automatically without accessing to /jobs – TomSawyer Sep 21 '19 at 13:47
  • @TomSawyer you can run executor anywhere you want. I put it in route because I want to run background tasks when user sent GET request. – Denys Synashko Sep 22 '19 at 22:33
  • @DenysSynashko because if i put executor outside, sometimes it blocked the main thread – TomSawyer Sep 23 '19 at 02:30
  • 2
    What if `some_long_task1` or `some_long_task2` raised exceptions? I have wrote similar codes, but I found that the exceptions were suppressed. – secsilm Dec 13 '19 at 08:24
  • Anything additional required for accessing a flask-sqlalchemy database from the background jobs? – psilocybin Dec 16 '19 at 13:40
  • @psilocybin I do not use it, I'm working with a raw SQLAlchemy and `scoped_session`, so don't know pitfalls with `flask-sqlalchemy`. New threads runs within the same python interpreter process. For `flask-sqlalchemy` you always need a flask app context. – Denys Synashko Jan 20 '20 at 09:01
  • by using this way, the main thread will be blocked sometimes? – TomSawyer Jan 30 '20 at 07:06
  • @TomSawyer I suggest you to learn about GIL - "The Python Global Interpreter Lock or GIL, is a mutex (or a lock) that allows only one thread to hold the control of the Python interpreter. This means that only one thread can be in a state of execution at any point in time." – Denys Synashko Jan 31 '20 at 12:06
  • @DenysSynashko I know about GIL, but in production environment, uwsgi spawns folked flask (running in multi threaded), then if i put an background crontab (loop) in one thread. somehow it blocks the main thread. Flask app will not serve request anymore https://www.reddit.com/r/flask/comments/d7qlrj/how_does_uwsgi_work_with_flask_for_non_blocking/ – TomSawyer Jan 31 '20 at 16:09
  • @TomSawyer I have no any problems with UWSGI and ThreadPoolExecutor in my tasks. I don't implement so strange things like a persistent loops in threads, lol. For scheduling I prefer something like task publisher + queue + worker subscribed to that queue. – Denys Synashko Feb 01 '20 at 03:04
27

Check out Flask-Executor which uses concurrent.futures in the background and makes your life very easy.

from flask_executor import Executor

executor = Executor(app)

@app.route('/someJob')
def index():
    executor.submit(long_running_job)
    return 'Scheduled a job'

def long_running_job
    #some long running processing here

This not only runs jobs in the background but gives them access to the app context. It also provides a way to store jobs so users can check back in to get statuses.

Dylan Anthony
  • 672
  • 7
  • 11
  • it has to be putted inside app context? – TomSawyer Sep 09 '19 at 09:26
  • This works brilliantly, I have completed some examples using this executor https://stackoverflow.com/questions/68411571/flask-socketio-emitting-a-pandas-dataframe-from-a-background-task-using-flask. However, can you please point to an example that evokes multiple background task that can run at the same time. – Sade Jul 20 '21 at 12:24
14

The best thing to do for stuff like this is use a message broker. There is some excellent software in the python world meant for doing just this:

Both are excellent choices.

It's almost never a good idea to spawn a thread the way you're doing it, as this can cause issues processing incoming requests, among other things.

If you take a look at the celery or RQ getting started guides, they'll walk you through doing this the proper way!

rdegges
  • 32,786
  • 20
  • 85
  • 109
  • 7
    yes, I knew about celery and redis queues. But, I am trying them to have as a simple thread background job. Not sure, what issues will pop up if I use threads. Can you explain. also, what change I need if I want to work the way I coded? Is it not possible at all? – San Mar 24 '14 at 18:16
  • I'm interested in knowing how this could impact request processing please. – Konrad Jan 22 '20 at 16:50
  • @San I suppose it's because python uses GIL and python's threads are not threads in their classic sense. **Even multithreaded application is still single threaded in python**. Threads in python help with IO mostly, but anything other than that in fact **could** impact request processing. And threads in python are not lightweight, so they do take up a chunk of resources. – winwin Sep 25 '21 at 15:36
1

If you'd like to execute the long-running operation within the flask application context, then it's a bit easier to (as opposed to using ThreadPoolExecutor, taking care of exceptions):

  1. Define a command line for your application (cli.py) - because all web applications should have an admin cli anyway.
  2. subprocess.Popen (no wait) the command line in a web request.

For example:

# cli.py

import click
import yourpackage.app
import yourpackage.domain

app = yourpackage.app.create_app()

@click.group()
def cli():
    pass

@click.command()
@click.argument('foo_id')
def do_something(foo_id):
    with app.app_context():
        yourpackage.domain.do_something(foo_id)

if __name__ == '__main__':
    cli.add_command(do_something)
    cli()

Then,

# admin.py (flask view / controller)

bp = Blueprint('admin', __name__, url_prefix='/admin')

@bp.route('/do-something/<int:foo_id>', methods=["POST"])
@roles_required('admin')
def do_something(foo_id):
    yourpackage.domain.process_wrapper_do_something(foo_id)
    flash("Something has started.", "info")
    return redirect(url_for("..."))

And:

# domain.py

import subprocess

def process_wrapper_do_something(foo_id):
    command = ["python3", "-m", "yourpackage.cli", "do_something", str(foo_id)]
    subprocess.Popen(command)

def do_something(foo_id):
    print("I am doing something.")
    print("This takes some time.")
turdus-merula
  • 8,546
  • 8
  • 38
  • 50
0

Agree with the marked answer from @rdegges. Sorry my account doesn't have enough credit to add comment under the answer, but I want to make it clear on "Why to use a message broker, instead of spawning a thread (or process)".

The other answers about ThreadPoolExecutor and flask_executor are creating a new thread (or process, as flask_executor is capable of) to execute the "long_running_job". These new threads/processes will have the same context as the main website:

For threads: The new thread will be able to access the context of the website application, change things, or break it, if this thread raises an exception; For processes: The new process will have a copy of the context of the website application. If the website somehow use a lot of memory in initialization, the new process will have a copy of it too, even if the process is not going to utilize this part of the memory.

On another hand, if you are using a message broker, and another application to retrieve the job message to work on it, the new application will have nothing to do with the website application, also it doesn't copy the memory from the web app.

In the future, when your application is big enough, you can place your application into another server (or servers), It is easy to scale out.

Ben L
  • 171
  • 1
  • 9
0

If you want use Celery with flask. Firstly check your operation and when operation completed redirect your celery task. You can check this link if you dont now celery: FLASK: How to establish connection with mysql server?

Ayse
  • 576
  • 4
  • 13