может ли мой веб-сервер зависнуть, если я использую эти блокирующие вызовы?Точнее, возможно ли, что цикл обработки событий будет пропускать события из-за блокировки кода?
Сервер будет точно зависать на время выполнения функций изображения.Вы не пропустите ни одного события, но вся обработка событий будет отложена на время выполнения функций изображения.
Заморозка цикла событий - плохая ситуация - вам следует избегать этого.
Если да, что является заменой для этих функций, которые интегрируются с asyncio?нет версии asyncio для PIL.
Самый простой и универсальный способ избежать замораживания цикла событий - выполнить функцию блокировки в другом потоке или другом процессе, используя asyncio.run_in_executor .Фрагмент кода показывает, как это сделать, и содержит хорошее объяснение, когда использовать процесс или поток:
def blocking_io():
# File operations (such as logging) can block the
# event loop: run them in a thread pool.
with open('/dev/urandom', 'rb') as f:
return f.read(100)
def cpu_bound():
# CPU-bound operations will block the event loop:
# in general it is preferable to run them in a
# process pool.
return sum(i * i for i in range(10 ** 7))
Я только хочу добавить, что пул процессов не всегда может быть хорошим решением для всех операций, связанных с процессором.Если функции работы с изображениями не занимают много времени (или, особенно, если на вашем сервере нет нескольких процессорных ядер), все равно может быть более продуктивно запускать их в потоке.
В общем, чтосчитается «блокирующим кодом» в asyncio?кроме очевидных операций, таких как сокет, чтение файла и т. д. Например, считается ли os.path.join () нормально?как насчет работы с пустым массивом?
Грубо говоря, любая функция блокирует: она на некоторое время блокирует цикл обработки событий.Но многие функции, такие как os.path.join
, занимают так мало времени, поэтому они не являются проблемой, и мы не называем их «блокирующими».
Трудно сказать точный предел времени выполнения (и замораживания цикла событий)становится проблемой, особенно учитывая, что это время будет отличаться для разных аппаратных средств.Мой предвзятый совет - если ваш код занимает (или может занять)> 50 мс перед возвратом управления в цикл обработки событий, считайте его блокирующим и используйте run_in_executor
.
Upd:
Спасибо, имеет ли смысл использовать один цикл событий (основного потока) и другой поток, который будет добавлять задачи с использованием того же цикла?
Я не уверенчто вы имеете в виду здесь, но я думаю, что нет.Нам нужен другой поток, чтобы выполнить какое-то задание, а не добавлять туда задания.
Мне нужен какой-то способ, чтобы поток информировал основной поток после завершения обработки изображения`
Просто дождитесь результата run_in_executor
или начните задание с него.run_in_executor
- сопрограмма, выполняющая что-либо в фоновом потоке без блокировки цикла событий.
Это будет выглядеть так:
thrad_pool = ThreadPoolExecutor()
def process_image(img):
# all stuff to process image here
img.save()
img.resize()
async def async_image_process(img):
await loop.run_in_executor(
thread_pool,
partial(process_image, img)
)
async def handler(request):
asyncio.create_task(
async_image_process(img)
)
# we use task to return response immediately,
# read https://stackoverflow.com/a/37345564/1113207
return web.Response(text="Image processed without blocking other requests")