繁体   English   中英

使用特定的 conda 虚拟环境将 Python 程序作为 Windows 服务运行

[英]Running Python Program as Windows Service with a specific conda virtual environment

我正在尝试运行一个使用 Anaconda 编写的 python 程序作为 Windows 服务。 复杂的是我想从特定的 conda 虚拟环境运行 Windows 服务。 这个想法是在未来,我们可能会开发更多基于 python 的 Windows 服务,这些服务可能具有不同的模块依赖关系,因此将每个服务保持在自己的虚拟环境中将是理想的。

我找到了几篇关于如何将 python 程序编写为 Windows 服务的优秀文章,它们运行良好。 我创建了一个非常简单的测试程序,它只是在服务启动后将一些消息写入文本文件。 我可以成功地将这个测试 python 程序安装为 Windows 服务,并且我在我的文件中看到了各种文本消息。 但是,当我尝试将 Numpy 或 TensorFlow 之类的模块导入到我的简单测试 Python 程序中时,该服务将无法启动,并且我收到无法找到它们各自 DLL 的失败消息。

我确定问题是因为所需的 conda 虚拟环境尚未激活。 同时,我尝试在系统级别复制各种 conda 环境变量; 尝试将所有必需的python库路径从虚拟环境添加到系统路径和系统范围的python路径,但无济于事。

我怀疑如果我可以激活 conda 虚拟环境作为我的 python 代码的一部分,那将解决问题。 (我还怀疑将所有必需的模块安装到我的基本配置中会解决问题,但我想避免这种情况)。

这是我编写的小测试程序。 该程序与基本的 Python 模块(如 sys、os 等)一起工作得很好。 当我尝试运行它并包含 Numpy 或 TensorFlow 时,它失败并显示以下错误消息:(这是我尝试启动服务后的 Windows 事件查看器 - 安装正确):

Python 无法导入服务的模块 Traceback(最近一次调用最后一次):文件 "D:\\TFS\\Projects\\DEV\\AEPEnrollmentForms\\src\\aepenrl\\Windows_Service_Example.py",第 35 行,在 import numpy as np File "C:\\ Users\\pboerner\\AppData\\Local\\conda\\conda\\envs\\aepenr\\lib\\site-packages\\numpy__init__.py”,第 140 行,来自 . 导入 _distributor_init 文件“C:\\Users\\pboerner\\AppData\\Local\\conda\\conda\\envs\\aepenr\\lib\\site-packages\\numpy_distributor_init.py”,第 34 行,从 . 导入 _mklinit 导入错误:DLL 加载失败:找不到指定的模块。 %2: %3

这是简单测试程序的代码。 (我从 Davide Mastromatteo 提供的一篇优秀文章中获取的大部分 Windows 服务集成工作)

import numpy as np

import socket
import sys
import time

import win32serviceutil

import servicemanager
import win32event
import win32service


class SimpleService(win32serviceutil.ServiceFramework):
    '''Base class to create winservice in Python'''

    _svc_name_ = 'TestPythonSrvc'
    _svc_display_name_ = 'Test Python Service'
    _svc_description_ = 'Test to see how to create a windows service with python'

    @classmethod
    def parse_command_line(cls):
        '''
        ClassMethod to parse the command line
        '''
        win32serviceutil.HandleCommandLine(cls)

    def __init__(self, args):
        '''
        Constructor of the winservice
        '''
        self.isrunning=True
        self.fid = open("D:\\temp\\simple_service.txt", "w")
        self.fid.write("Initialize\n")
        self.fid.flush()

        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        socket.setdefaulttimeout(60)

    def SvcStop(self):
        '''
        Called when the service is asked to stop
        '''
        self.stop()
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self):
        '''
        Called when the service is asked to start
        '''
        self.start()
        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                              servicemanager.PYS_SERVICE_STARTED,
                              (self._svc_name_, ''))
        self.main()

    def start(self):
        '''
        Override to add logic before the start
        eg. running condition
        '''
        self.isrunning = True
        self.fid.write("Start method called\n")
        self.fid.flush()

    def stop(self):
        '''
        Override to add logic before the stop
        eg. invalidating running condition
        '''
        self.isrunning = False
        self.fid.write("STOP method called. Setting stop flag\n")
        self.fid.flush()

    def main(self):
        '''
        Main class to be ovverridden to add logic
        '''
        a = np.zeros((100,1))
        while True:
            if self.isrunning:
                self.fid.write(f"Tick. Numpy array shape {a.shape}\n")
                self.fid.flush()
                time.sleep(1)
            else:
                self.fid.write("Breaking out of main loop\n")
                self.fid.flush()
                break;

        self.fid.write("Closing the log file\n")
        self.fid.flush()
        self.fid.close()

if __name__ == '__main__':
    # This code block was required to get this simple service example to run
    # on a Windows 10 laptop with Admin privs.  Only calling the 
    # HandleCommandLine method alone didn'd seem to work. Not sure why but this
    # code was provided as a solution on the Web.
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(SimpleService)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(SimpleService)

不幸的是,Windows 的服务管理器不如 systemd 灵活。 我发现的唯一方法是执行以下操作:

  • 制作一个包含所有逻辑的批处理文件,例如
    call C:\\ProgramData\\Anaconda3\\Scripts\\activate.bat C:\\ProgramData\\Anaconda3 call activate yourenv cd C:/path/to/your/wd python yourservice.py and your args
    注意:您的 activate.bat 文件可能位于您的主文件夹下:~\\AppData\\local\\Continuum\\anaconda3\\Scripts
  • 使用 NSSM(名称恰如其分): http : //nssm.cc/download 另请参阅将批处理文件作为 Windows 服务运行 在调用 nssm 之前,您需要以管理员身份启动命令提示符或 powershell。

这可以很好地为bokeh服务器提供服务(使用bokeh serve而不是python )。 我想它适用于任何复杂的 python 脚本。

您可以在“挂钩”选项卡中停止或退出时运行命令。

在我的情况下,批处理文件中的逻辑取决于机器,所以我需要制作一个额外的 python 脚本,在设置编写批处理文件时调用该脚本。

当我遇到一个博客(下面的链接)并按照其中的步骤进行操作时,我陷入了完全相同的情况

  1. 使用以下几行在您的工作目录中创建一个 run.bat 文件。

     call conda activate env call python app.py

    因此,当您调用批处理文件时,它将首先激活虚拟环境,然后运行 ​​python 脚本。

  2. 下载NSSM ,无需安装。 下载后,转到 NSSM -> win32/win64(根据您的计算机架构)并在命令提示符中以管理员身份运行以下命令:

     C:\\nssm-2.24\\win64\\nssm install <service name> "C:\\path\\to\\your\\run.bat"
  3. 您的服务此时已安装但尚未启动。

  4. 您可能需要配置它

    C:\\nssm-2.24\\win64\\nssm edit <service name>
  5. 执行上述命令后将打开 NSSM 服务编辑器,您可以在其中设置显示名称、描述、日志文件等。

  6. 完成后,您可以启动该服务

    C:\\nssm-2.24\\win64\\nssm start <service name>

参考链接:- 在自己的 Python 虚拟环境中运行 Windows 服务

我只是按照这些步骤操作,对我来说效果很好。 我希望它也适用于你。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM