Estou escrevendo um aplicativo Python + GObject que precisa ler uma quantidade não trivial de dados do disco no início. Os dados são lidos de forma síncrona e leva cerca de 10 segundos para concluir a operação de leitura, período durante o qual o carregamento da interface do usuário é atrasado.
Gostaria de executar a tarefa de forma assíncrona e receber uma notificação quando estiver pronta, sem bloquear a interface do usuário, mais ou menos como:
def take_ages():
read_a_huge_file_from_disk()
def on_finished_long_task():
print "Finished!"
run_long_task(task=take_ages, callback=on_finished_long_task)
load_the_UI_without_blocking_on_long_task()
Eu usei o GTask no passado para esse tipo de coisa, mas estou preocupado que seu código não seja tocado em três anos, muito menos tenha sido portado para o GObject Introspection. Mais importante, ele não está mais disponível no Ubuntu 12.04. Portanto, estou procurando uma maneira fácil de executar tarefas de forma assíncrona, tanto no modo Python padrão quanto no GObject / GTK +.
Edit: aqui está um código com um exemplo do que estou tentando fazer. Tentei python-defer
conforme sugerido nos comentários, mas não consegui executar a tarefa longa de forma assíncrona e deixar a interface do usuário carregar sem ter que esperar a conclusão. Procure o código de teste .
Existe uma maneira fácil e amplamente usada de executar tarefas assíncronas e ser notificado quando elas terminarem?
fonte
async_call
função pode ser o que eu preciso. Você se importaria de expandir um pouco e adicionar uma resposta, para que eu possa aceitá-la e lhe creditar depois de testá-la? Obrigado!Respostas:
Seu problema é muito comum, portanto, existem inúmeras soluções (galpões, filas com multiprocessamento ou encadeamento, pools de trabalhadores, ...)
Por ser tão comum, também existe uma solução embutida em python (na 3.2, mas com suporte aqui: http://pypi.python.org/pypi/futures ) chamada concurrent.futures. 'Futuros' estão disponíveis em vários idiomas; portanto, python os chama da mesma forma. Aqui estão as chamadas típicas (e aqui está o seu exemplo completo , no entanto, a parte db é substituída por sleep, veja abaixo o porquê).
Agora, para o seu problema, que é muito mais complicado do que o seu exemplo simples sugere. Em geral, você tem threads ou processos para resolver isso, mas aqui está o porquê de seu exemplo ser tão complicado:
slow_load
do banco de dados não são selecionáveis, o que significa que eles não podem simplesmente ser passados entre processos. Portanto: não há multiprocessamento com resultados do softwarecenter!print
, nenhum estado do gtk muda, exceto a adição de um retorno de chamada!threads_init
e, se você chamar um método gtk ou semelhante, precisará protegê-lo (nas versões anteriores, isso eragtk.gdk.threads_enter()
,gtk.gdk.threads_leave()
veja. Por exemplo, gstreamer: http://pygstdocs.berlios.de/pygst-tutorial/playbin. html ).Eu posso lhe dar a seguinte sugestão:
slow_load
para retornar resultados selecionáveis e usar futuros com processos.Como uma nota: as soluções dadas pelos outros (
Gio.io_scheduler_push_job
,async_call
) fazer o trabalho comtime.sleep
mas não comsoftwarecenter.db
. Isto é, porque tudo se resume a threads ou processos e threads para não funcionar com o gtk esoftwarecenter
.fonte
Aqui está outra opção usando o GIO I / O Scheduler (nunca o usei antes no Python, mas o exemplo abaixo parece funcionar bem).
fonte
Você também pode usar o GLib.idle_add (retorno de chamada) para chamar a tarefa de longa execução quando o Mainloop do GLib concluir todos os seus eventos de maior prioridade (que eu acredito que inclui a construção da interface do usuário).
fonte
callback
é chamado, isso seria feito de forma síncrona, bloqueando a interface do usuário, certo?idle_add
é que o valor de retorno do retorno de chamada é importante. Se for verdade, será chamado novamente.Use a
Gio
API introspectada para ler um arquivo, com seus métodos assíncronos, e ao fazer a chamada inicial, faça isso como um tempo limite comGLib.timeout_add_seconds(3, call_the_gio_stuff)
wherecall_the_gio_stuff
é uma função que retornaFalse
.O tempo limite aqui é necessário adicionar (embora seja necessário um número diferente de segundos), porque, embora as chamadas Gio assíncronas sejam assíncronas, elas não são bloqueadoras, o que significa que a atividade pesada do disco de ler um arquivo grande ou grande número de arquivos, pode resultar na interface do usuário bloqueada, pois a interface do usuário e a E / S ainda estão no mesmo encadeamento (principal).
Se você deseja escrever suas próprias funções para ser assíncrono e integrar-se ao loop principal, usando as APIs de E / S de arquivos do Python, precisará escrever o código como um GObject, ou repassar retornos de chamada ou usar
python-defer
para ajudá-lo. faça. Mas é melhor usar o Gio aqui, pois ele pode oferecer muitos recursos interessantes, principalmente se você estiver abrindo / salvando arquivos no UX.fonte
Gio
API. O que eu queria saber é se existe uma maneira de executar qualquer tarefa genérica de longa duração de forma assíncrona, da mesma forma que o GTask costumava fazer.Acho que vale a pena notar que essa é uma maneira complicada de fazer o que o @mhall sugeriu.
Essencialmente, você pode executar isso e executar a função async_call.
Se você quiser ver como ele funciona, você pode jogar com o temporizador e continuar clicando no botão. É essencialmente o mesmo que a resposta de @ mhall, exceto que há um código de exemplo.
Com base nisso, esse não é o meu trabalho.
Observação adicional: você deve deixar o outro encadeamento terminar antes que ele termine corretamente ou procurar um arquivo.lock no encadeamento filho.
Editar para endereçar o comentário:
Inicialmente esqueci
GObject.threads_init()
. Evidentemente, quando o botão disparou, ele inicializou o threading para mim. Isso mascarou o erro para mim.Geralmente, o fluxo é criar a janela na memória, iniciar imediatamente o outro encadeamento, quando o encadeamento terminar atualizar o botão. Adicionei um sono adicional antes mesmo de ligar para Gtk.main para verificar se a atualização completa PODE ser executada antes mesmo da janela ser desenhada. Também comentei isso para verificar se o lançamento do thread não impede o desenho da janela.
fonte
slow_load
ser executado logo após o início da interface do usuário, mas nunca parece ser chamado, a menos que o botão seja clicado, o que me confunde um pouco, pois pensei que o objetivo do botão era apenas fornecer indicação visual do estado da tarefa.async_call
neste exemplo funciona para mim, mas gera confusão quando eu o porto para o meu aplicativo e adiciono aslow_load
função real que tenho.