Domanda

Sto incorporando l'interprete Python in un programma C. Tuttavia, potrebbe accadere che durante l'esecuzione di alcuni script Python tramite PyRun_SimpleString () venga eseguito in un ciclo infinito o eseguito per troppo tempo. Considera PyRun_SimpleString (" while 1: pass "); Nel prevenire il blocco del programma principale ho pensato di poter eseguire l'interprete in un thread.

Come posso interrompere l'esecuzione dello script Python nell'interprete incorporato in esecuzione in un thread senza interrompere l'intero processo?

È possibile passare un'eccezione all'interprete? Dovrei avvolgere la sceneggiatura sotto qualche altra sceneggiatura che ascolti i segnali?

PS: potrei eseguire il python in un processo separato ma questo non è quello che voglio - a meno che non sia l'ultima risorsa ...


Aggiornamento:

Quindi funziona ora. Grazie Denis Otkidach, ancora una volta!

Se vedo bene, devi fare due cose: dire all'interprete di fermarsi e return -1 nello stesso thread in cui PyRun_SimpleString () è in esecuzione.

Per interrompere, si hanno alcune possibilità: PyErr_SetString (PyExc_KeyboardInterrupt, " ... ") o PyErr_SetInterrupt () - il primo potrebbe lasciare Python in esecuzione qualche altra istruzione e poi si interrompe, quella successiva interrompe immediatamente l'esecuzione.

Per return -1 si utilizza Py_AddPendingCall () per iniettare una chiamata di funzione nell'esecuzione di Python. I documenti lo citano dalla versione 2.7 e 3.1 ma funziona anche su Pythons precedenti (2.6 qui). Da 2.7 e 3.1 dovrebbe anche essere thread-safe, il che significa che puoi chiamarlo senza acquisire GIL (?).

Quindi si potrebbe riscrivere il seguente esempio:

int quit() {
    PyErr_SetInterrupt();
    return -1;
}
È stato utile?

Soluzione

Puoi usare Py_AddPendingCall () per aggiungere una funzione che solleva l'eccezione da chiamare al prossimo intervallo di controllo (vedi i documenti su sys.setcheckinterval () per maggiori informazioni). Ecco un esempio con la chiamata Py_Exit () (che funziona per me, ma probabilmente non è quello che ti serve), sostituiscilo con Py_Finalize () o uno di PyErr_Set * () :

int quit(void *) {
    Py_Exit(0);
}


PyGILState_STATE state = PyGILState_Ensure();
Py_AddPendingCall(&quit, NULL);
PyGILState_Release(state);

Questo dovrebbe essere sufficiente per qualsiasi codice pure-python. Tuttavia, nota che alcune funzioni C possono essere eseguite per un po 'come un'unica operazione (c'era un esempio con una ricerca regexp a esecuzione prolungata, ma non sono sicuro che sia ancora rilevante).

Altri suggerimenti

Bene, l'interprete di Python dovrebbe essere in esecuzione in un thread separato dal programma di incorporamento o semplicemente non avrai mai la possibilità di interrompere l'interprete.

Quando lo hai, forse puoi usare una delle chiamate di eccezione dell'API Python per attivare un'eccezione nell'interprete? Vedi Eccezioni API Python C

Volevo essere in grado di interrompere qualsiasi blocco di codice Python in esecuzione attivamente, incluso l'annullamento di un ciclo infinito o solo un codice di lunga durata. Nella mia applicazione, viene inviato e valutato il codice Python a thread singolo. Una richiesta di interruzione può arrivare in qualsiasi momento. Questa domanda e risposta sono state fondamentali per aiutarmi a realizzarlo.

Guardando questa domanda nel 2019, la soluzione qui non ha funzionato per me; Ho provato a usare Py_AddPendingCall (& amp; quit, NULL); in molti modi diversi ma la funzione chiamata non è mai stata eseguita come previsto, o per niente. Ciò ha causato l'arresto del motore Python e, infine, l'arresto anomalo quando ho inviato Py_FinalizeEx () . Alla fine ho trovato ciò di cui avevo bisogno in questa risposta molto utile a una domanda simile.

Dopo aver avviato il motore Python, recupero l'id del thread usando il pacchetto threading nel codice Python. Lo analizzo in un valore lungo e lo salvo.

La funzione di interruzione è in realtà abbastanza semplice:
PyGILState_Ensure ()
PyThreadState_SetAsyncExc (threadId, PyExc_Exception) utilizzando l'ID thread che ho salvato in precedenza e un tipo di eccezione generico
PyGILState_Release ()

scroll top