监控可执行文件的进度,以便在网络表单上报告

Monitor progress of executable so it can be reported on webform

我最近一直在考虑采用一个遗留建筑分析程序并为其提供一个带有 asp.NET Webforms 和 javascript 的前端。 (遗留代码是用 Fortran 语言编写的,目的是 运行 在 Web 服务器或另一台远程计算机上。)

问题:是否可以监视可执行文件的进程(运行在 Web 服务器或其他地方),以便可以在 Web 表单上报告?

遗留代码仍然是正在进行的开发计划的一部分。我们不打算将其重新编码为 C# 等,因为:

1) 性能至上;和

2) 这将是一项艰巨的任务。

但是可以修改 Fortran 代码以输出某些内容或调用某些内容来更新进度并让用户知道(希望通过 Webform)可执行文件的运行情况。

有没有人遇到或应用过可以提供此功能的编程模型?

FORTRAN 程序可以将状态(比如开始于 25/02/2015 12:05:57,完成于 25/02/2015 12:08:38)写入文本文件或数据库 table.在状态网页上,您可以使用自动刷新或 jQuery ajax 调用来检查进程的状态并更新它。

我发现在 Fortran 中执行 I/O 的最简单方法是使用其本机 .dat 文件格式。它是二进制的,但它是一种相对合理的格式,允许存储具有任何类型数组数据的多条记录。要了解如何在 Fortran 上下文之外处理这些类型的文件,您可以查看 following python script 中的 unpackNextRecord 函数。这应该相对容易翻译成任何语言(以防那里尚不存在)。

现在,在 Fortran 方面,您可以使用类似

的函数来创建这些文件
subroutine writeToFile[MyDataType](path, array) 
  implicit none 
  [MyDataType], intent(in), dimension(:) :: array 
  character(len=*), intent(in) :: path 
  character(len=:), allocatable :: dirname 
  integer(4) :: imt 

  dirname = getDirectory(path) 
  call makeDirectory(dirname) 
  call findNewFileHandle(imt) 
  open(imt, file = path, form = 'unformatted', status = 'replace') 
  write(imt) array 
  close(imt) 
  deallocate(dirname) 
end subroutine

您还可以通过这种方式多次使用不同的数据类型写入一个文件句柄,例如,如果您有一些字符串和一组数字要保存 - 只需添加更多 write(imt) 调用和输入到你的助手子程序。要获取此处使用的辅助函数 makeDirectoryfindNewFileHandle,请参阅 the following Fortran 90 module

如果您习惯于处理文本文件,可能会觉得这些东西很奇怪,但在 Fortran 中,这种方式更安全、更舒适 - 如果您想编写纯文本,您会遇到很多困难,直到您明白没错,我告诉你。

要制作不影响代码性能的进度条之类的东西,您可以这样做。

  1. 在模块中创建全局变量

    module progress
      implicit none
      real*8           :: progress_value    = huge(1.d0)
      integer          :: progress_bar(2)   = (/ 1, 1 /)
      integer          :: progress_refresh  = 2
      integer          :: progress_unit     = 66
      logical          :: progress_active   = .False.
      character*(64)   :: progress_title    = "UNDEFINED"
      character*(128)  :: progress_filename = "/tmp/progress"
    end module
    
    • progress_value :您要监控的值(例如 运行 平均值)
    • progress_bar :第一个索引将包含当前循环计数,第二个索引将包含总循环计数。比率 dble(progress_bar(1))/dble(progress_bar(2)) 将为您提供当前进度百分比。
    • progress refresh :每次刷新之间的时间间隔(以秒为单位)
    • progress_unit :您将在其中写入信息的文件的单位编号
    • progress_active : .True. 当你正在监控时,.False. 否则。
    • progress_title :让您确定您正在监控的内容。
    • progress_filename : 存储进度的文件名
  2. 创建 start/stop 监控的例程

    初始化监控:

    subroutine start_progress(max,title,progress_init)
      use progress
      implicit none
      integer, intent(in)       :: max
      character*(*), intent(in) :: title
      real*8, intent(in)        :: progress_init
    
      progress_bar(1) = 0
      progress_bar(2) = max
      progress_title = title
      progress_active = .True.
      progress_value = progress_init
      call run_progress()
    end
    
    • max : 循环次数的最大值
    • title : 您监控的标题
    • progress_init : progress_value
    • 的初始值

    终止监听:

    subroutine stop_progress()
      use progress
      implicit none
      progress_active = .False.
      call write_progress()
    end
    
  3. 打印进度文件中数据的子程序

    subroutine write_progress()
      use progress
      implicit none
      open(unit=progress_unit,file=progress_filename)
      write(progress_unit,'(A)') progress_title
      write(progress_unit,'(F5.1,X,A)') dble(100*progress_bar(1))/dble(progress_bar(2)), '%'
      write(progress_unit,*) progress_value
      close(unit=progress_unit)
    end
    
  4. 以及使用ALARM信号的核心

    recursive subroutine run_progress()
      use progress
      implicit none
      call write_progress()
      if (progress_active) then
        call alarm(progress_refresh,run_progress)
      endif
    end
    
  5. 你是这样使用它的:

    program test
      use progress
      implicit none
    
      integer :: i
      integer, parameter :: nmax = 10
    
      double precision :: s
    
    ! --> Before the loop, run start_progress
      s = 0.d0
      call start_progress(nmax, "Dummy loop", s)
      do i=1,nmax
        call sleep(1)
        s = s+1.d0
    
    ! --> Update the global variables at the end of each loop cycle
    !     (there is no function call, so it is almost free)
    
        progress_value  = s
        progress_bar(1) = i
      enddo
    
    ! --> After the loop, call stop_progress
      call stop_progress()
    end
    

如果您不想使用文件而是命名管道(在编写例程中没有 open/close 语句), 由于其缓冲输出,它可能不适用于 Fortran。你可以试试

    export GFORTRAN_UNBUFFERED_ALL=1

在您的 shell 中进行更改。

您可以在这里获取代码:https://gist.github.com/scemama/9977dc1290685b541f9c

我更喜欢从 C# 调用长 运行 Fortran 例程是将 Fortran 例程编译为 DLL,然后将回调函数从 C# 传递到 Fortran 例程。这里的优点是您不会有多个进程争用同时访问同一文件。

module MyLib

    implicit none

    interface
        subroutine ProgressUpdateAction(percentProgress)
            integer, intent(in) :: percentProgress
        end subroutine
    end interface   

contains

    subroutine DoWork(progressUpdate)

        !DIR$ ATTRIBUTES DLLEXPORT :: DoWork
        !DIR$ ATTRIBUTES ALIAS: 'DoWork' :: DoWork
        !DIR$ ATTRIBUTES REFERENCE :: progressCallBack

        procedure(ProgressUpdateAction), intent(in), pointer :: progressUpdate

        integer, parameter :: STEP_COUNT = 9
        integer :: i, percentProgress

        do i = 1, STEP_COUNT

            ! Update the status.
            percentProgress = (i - 1) * 100.0 / STEP_COUNT
            call progressUpdate(percentProgress)

            ! Do something expensive...

        end do

    end subroutine

end module

在 C# 调用例程中创建一个匹配的委托。请记住通过引用传递所有内容 - Fortran 默认值。

public static class FortranLib
{
    private const string _dllName = "FortranLib.dll";

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ProgressUpdateAction(ref int progress); // Important the int is passed by ref (else we could use built-in Action<T> instead of delegate).

    [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public static extern void DoWork([MarshalAs(UnmanagedType.FunctionPtr)] ref ProgressUpdateAction progressUpdate);
}

class Program
{
    static void Main(string[] args)
    {
        try
        {
            FortranLib.ProgressUpdateAction updateProgress = delegate(ref int p)
            {
                Console.WriteLine("Progress: " + p + "%");
            };

            FortranLib.DoWork(ref updateProgress);

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

这里有完整的细节:http://www.luckingtechnotes.com/calling-fortran-from-c-monitoring-progress-using-callbacks/