Delphi и Python embedded
Есть у меня программка, которая предназначена для запуска скриптов Python из Delphi.
Использую я Python embedded, так что для этой программы нет необходимости установить Python в компьютере. Недавно я хотел свою программу на Delphi совершенствовать. Проблема была в том, что скрипты запускались в основном потоке, из-за чего UI программы зависал до окончания работы скрипта Python. А некоторые скрипты работали довольно долго. Не долго думая, решил использовать TTask. Вот фрагмент программы, запускающий скрипт Python-а в отдельный поток Delphi:
task := TTask.Create(
procedure
begin
GetPythonEngine.ExecFile(PyFileName);
if task.Status = TTaskStatus.Canceled then
//Если отменили, выходим из цикла.
Exit;
end);
task.Start;
Запускаю программу, вроде все работает. Но в скриптах где импортируется библиотека numpy скрипт зависает. Эти же скрипты в основном потоке работали. Но тут никак. Ошибка не исчезла. Пришлось открыть запрос в гитхабе. Там мне автор подсказал статью https://github.com/pyscripter/python4delphi/wiki/PythonThreads, где довольно подробно объясняется запуск скриптов в отдельном потоке.
Первый вариант:
ThreadPythonExec(
procedure
begin
GetPythonEngine.ExecFile(PyFileName);
end);
Второй вариант:
Task := TTask.Create(
procedure
var
Py: IPyEngineAndGIL;
begin
Py := SafePyEngine;
Py.PythonEngine.ExecFile(PyFileName);
end);
Task.Start;
Там есть еще и другие варианты, но сейчас не об этом. Но опять проблема не решена. И оказывается при создании формы следует вызывать следующий код:
TPythonThread.Py_Begin_Allow_Threads;
А при закрытии формы:
TPythonThread.Py_End_Allow_Threads;
И вроде все работает, проблема решена. Но не тут-то было.
У меня MDI-приложение. И мой PythonEngine был помещен в форме со списками скриптов. И когда я закрывал эту форму и заново вызывал форму, то скрипт с импортом numpy опять не работал и выдавал следующее сообщение:
C:\A\34\s/Objects/structsseq.c:398: bad argument to internal function
Тогда я решил при уничтожении формы делать следующее:
PyEngine.UnloadDll;
TPythonThread.Py_End_Allow_Threads; // Acquire the GIL
FreeAndNil(PyEngine);
Но ничего не изменилась. Обратился к автору. Ответ автора:
Почему вы освобождаете PythonEngine? Уничтожение и воссоздание PythonEngine - не самая лучшая идея.
Почему я не могу пересоздавать PythonEngine я так и не понял.
Решение проблемы.
Пришлось перенести PythonEngine и PythonInputOutput в DataModule. При создании DataModule:
// Prepare Python Engine
MaskFPUExceptions(True);
PyEngine.DllPath := glSetup.AppPath + 'DLLs';
PyEngine.DllName := 'python38.dll';
PyEngine.APIVersion := 1013;
PyEngine.AutoLoad := False;
PyEngine.LoadDll;
TPythonThread.Py_Begin_Allow_Threads; // Release the GIL
А в форме со списком скриптов
procedure TfrmScripts.PyInOutSendData(Sender: TObject; const Data: AnsiString);
begin
PyConsole.Lines.Add(Data);
end;
procedure TfrmScripts.PyInOutSendUniData(Sender: TObject; const Data: string);
var S: String;
begin
if IsUTF8String(Data) then
S := UTF8Decode(Data)
else
S := Data;
PyConsole.Lines.Add(S);
end;
PyConsole у меня это компонента TSynEdit. Все данные со скриптов выводятся в TSynEdit.
При создании формы со списками скриптов:
DMain.PyInOut.OnSendData := PyInOutSendData;
DMain.PyInOut.OnSendUniData := PyInOutSendUniData;
И вот, проблема решена. Все прекрасно работает.