Asynchronous Tcl中的异步文件IO

Asynchronous Tcl中的异步文件IO,asynchronous,tcl,Asynchronous,Tcl,我有一个用Tcl包装的C函数,它打开一个文件,读取内容,执行一个操作,并返回一个值。不幸的是,当我调用该函数打开一个大文件时,它会阻塞事件循环。操作系统是linux 我想使这些调用异步。我该怎么做 我可以将工作传递给另一个Tcl线程,但这并不是我想要的。Tcl确实支持其通道上的异步I/O,因此包括使用事件样式回调方法的文件 其思想是将一个脚本注册为对设置为非阻塞模式的打开通道上的所谓可读事件的回调,然后在该脚本中调用通道上的读取一次,处理读取的数据,然后测试读取操作是否达到EOF条件,在这种情况

我有一个用Tcl包装的C函数,它打开一个文件,读取内容,执行一个操作,并返回一个值。不幸的是,当我调用该函数打开一个大文件时,它会阻塞事件循环。操作系统是linux

我想使这些调用异步。我该怎么做


我可以将工作传递给另一个Tcl线程,但这并不是我想要的。

Tcl确实支持其通道上的异步I/O,因此包括使用事件样式回调方法的文件

其思想是将一个脚本注册为对设置为非阻塞模式的打开通道上的所谓可读事件的回调,然后在该脚本中调用通道上的读取一次,处理读取的数据,然后测试读取操作是否达到EOF条件,在这种情况下关闭文件

基本上是这样的:

set data ""
set done false

proc read_chunk fd {
  global data
  append data [read $fd]
  if {[eof $fd]} {
    close $fd
    set ::done true
  }
}

set fd [open file]
chan configure $fd -blocking no
chan event $fd readable [list read_chunk $fd]
vwait ::done
两点:Tcl案例中的a≤ 8.5您必须使用fconfigure而不是chan configure,使用fileevent而不是chan event;b如果您使用的是Tk,则不需要vwait,因为Tk已经强制运行Tcl事件循环

但要注意一点:如果您正在读取的文件位于物理连接的快速介质(如旋转磁盘)上,SSD等,它将是相当高的可用性,这意味着Tcl的事件循环将被文件上的可读事件所饱和,并且总体用户体验可能会比一口就读的更糟糕,因为Tk UI对其许多任务使用空闲优先级回调,在你的文件被读取之前,他们不会有任何机会运行;最终,你会有迟钝或冻结的用户界面,与一口喝完的情况相比,文件在挂钟时间段的读取速度会慢一些。 有两种可能的解决方案:

一定要使用单独的线程。 使用它可以让空闲优先级事件有机会运行-在回调脚本中,执行另一个具有空闲优先级的回调脚本的可读事件计划:

chan event $fd readable [list after idle [list read_chunk $fd]]
显然,这实际上使通过Tcl事件循环传输的事件数量增加了一倍,以响应文件数据块的可用性,但作为交换,它将处理文件数据的优先级降低到UI事件的优先级

您可能还想在可读回调中调用update来强制事件循环处理UI事件


自Tcl 8.6以来,还有另一种方法可用:。其主要思想是,不使用事件,而是使用合理的小数据块与其他处理交错读取文件。这两项任务都应该以协同程序的形式实现,周期性地相互让步,从而创建一个协作的多任务处理

Tcl在其通道上支持异步I/O,因此包括使用事件样式回调方法的文件

其思想是将一个脚本注册为对设置为非阻塞模式的打开通道上的所谓可读事件的回调,然后在该脚本中调用通道上的读取一次,处理读取的数据,然后测试读取操作是否达到EOF条件,在这种情况下关闭文件

基本上是这样的:

set data ""
set done false

proc read_chunk fd {
  global data
  append data [read $fd]
  if {[eof $fd]} {
    close $fd
    set ::done true
  }
}

set fd [open file]
chan configure $fd -blocking no
chan event $fd readable [list read_chunk $fd]
vwait ::done
两点:Tcl案例中的a≤ 8.5您必须使用fconfigure而不是chan configure,使用fileevent而不是chan event;b如果您使用的是Tk,则不需要vwait,因为Tk已经强制运行Tcl事件循环

但要注意一点:如果您正在读取的文件位于物理连接的快速介质(如旋转磁盘)上,SSD等,它将是相当高的可用性,这意味着Tcl的事件循环将被文件上的可读事件所饱和,并且总体用户体验可能会比一口就读的更糟糕,因为Tk UI对其许多任务使用空闲优先级回调,在你的文件被读取之前,他们不会有任何机会运行;最终,你会有迟钝或冻结的用户界面,与一口喝完的情况相比,文件在挂钟时间段的读取速度会慢一些。 有两种可能的解决方案:

一定要使用单独的线程。 使用它可以让空闲优先级事件有机会运行-在回调脚本中,执行另一个具有空闲优先级的回调脚本的可读事件计划:

chan event $fd readable [list after idle [list read_chunk $fd]]
显然,这实际上使通过Tcl事件循环传输的事件数量增加了一倍,以响应文件数据块的可用性,但作为交换,它将处理文件数据的优先级降低到UI事件的优先级

您可能还想在可读回调中调用update来强制事件循环处理UI事件

自Tcl 8.6以来,还有另一种方法可用:。其主要思想是,不使用事件,而是使用合理的小数据块与其他处理交错读取文件。这两个任务都应该作为协同程序来实现,定期地相互让步,从而创建一个
协同多任务处理

这通常很难做到。问题是,由于操作系统级别涉及抽象,异步文件操作不能很好地处理普通文件。最好的解决方法是(如果可以的话)首先在文件上建立一个索引,这样就可以避免通读所有文件,而只需寻找接近数据的地方。这是数据库工作原理的核心

如果您不能做到这一点,但可以应用一个简单的过滤器,那么将该过滤器放在子流程管道中可以与Tcl中的异步I/O一起工作,并且它们在所有受支持的平台或另一个线程上都可以这样做。从异步处理的角度来看,线程间消息也很好

如果可以,请使用上述技术。我认为你应该这样做

如果这是不切实际的,你将不得不以艰难的方式做到这一点。 困难的方法是在处理过程中插入事件循环感知延迟

在8.5及之前引入延迟 在Tcl 8.5及之前的版本中,您可以通过在不同的过程中将代码分成几部分,并使用类似这样的一节通过“延迟”在它们之间传递控制来实现这一点:

# 100ms delay, but tune it yourself
after 100 [list theNextProcedure $oneArgument $another]
这是一种连续传球方式,要想正确传球可能会相当棘手。特别是,它的处理相当复杂。例如,假设您正在对文件的前千行执行循环:

proc applyToLines {filename limit callback} {
    set f [open $filename]
    for {set i 1} {$i <= $limit} {incr i} {
        set line [gets $f]
        if {[eof $f]} break
        $callback $i $line
    }
    close $f
}
applyToLines "/the/filename.txt" 1000 DoSomething
在经典的Tcl CPS中,您可以这样做:

proc applyToLines {filename limit callback} {
    set f [open $filename]
    Do1Line $f 1 $limit $callback
}
proc Do1Line {f i limit callback} {
    set line [gets $f]
    if {![eof $f]} {
        $callback $i $line
        if {[incr i] <= $limit} {
            after 10 [list Do1Line $f $i $limit $callback]
            return
        }
    }
    close $f
}
applyToLines "/the/filename.txt" 1000 DoSomething
正如您所看到的,这不是一个简单的转换,如果您想在处理完成后做一些事情,您需要传递回调。您也可以使用globals,但这很难做到优雅

如果您需要帮助更改代码以使其正常工作,则需要向我们显示您需要帮助的代码

在8.6中引入延迟 在TCL8.6中,尽管上述代码技术仍然有效,但您还有另一个选择:协同路由!我们可以这样写:

proc applyToLines {filename limit callback} {
    set f [open $filename]
    for {set i 1} {$i <= $limit} {incr i} {
        set line [gets $f]
        if {[eof $f]} break
        yield [after 10 [info coroutine]]
        $callback $i $line
    }
    close $f
}
coroutine ApplyToAFile applyToLines "/the/filename.txt" 1000 DoSomething
这几乎是相同的,除了带有yield和info-coroutine的行,该行挂起该协程,直到它在大约10毫秒的时间内从事件循环中恢复,以及带有coroutine-ApplyToAFile的行,其中该前缀创建了一个具有给定任意名称ApplyToAFile的协程,并将其设置为运行。正如您所看到的,这样转换代码并不难


协程引擎的后端口完全不可能达到8.5或更高版本;它完全需要8.6中的非递归脚本执行引擎。

这通常很难做到。问题是,由于操作系统级别涉及抽象,异步文件操作不能很好地处理普通文件。最好的解决方法是(如果可以的话)首先在文件上建立一个索引,这样就可以避免通读所有文件,而只需寻找接近数据的地方。这是数据库工作原理的核心

如果您不能做到这一点,但可以应用一个简单的过滤器,那么将该过滤器放在子流程管道中可以与Tcl中的异步I/O一起工作,并且它们在所有受支持的平台或另一个线程上都可以这样做。从异步处理的角度来看,线程间消息也很好

如果可以,请使用上述技术。我认为你应该这样做

如果这是不切实际的,你将不得不以艰难的方式做到这一点。 困难的方法是在处理过程中插入事件循环感知延迟

在8.5及之前引入延迟 在Tcl 8.5及之前的版本中,您可以通过在不同的过程中将代码分成几部分,并使用类似这样的一节通过“延迟”在它们之间传递控制来实现这一点:

# 100ms delay, but tune it yourself
after 100 [list theNextProcedure $oneArgument $another]
这是一种连续传球方式,要想正确传球可能会相当棘手。特别是,它的处理相当复杂。例如,假设您正在对文件的前千行执行循环:

proc applyToLines {filename limit callback} {
    set f [open $filename]
    for {set i 1} {$i <= $limit} {incr i} {
        set line [gets $f]
        if {[eof $f]} break
        $callback $i $line
    }
    close $f
}
applyToLines "/the/filename.txt" 1000 DoSomething
在经典的Tcl CPS中,您可以这样做:

proc applyToLines {filename limit callback} {
    set f [open $filename]
    Do1Line $f 1 $limit $callback
}
proc Do1Line {f i limit callback} {
    set line [gets $f]
    if {![eof $f]} {
        $callback $i $line
        if {[incr i] <= $limit} {
            after 10 [list Do1Line $f $i $limit $callback]
            return
        }
    }
    close $f
}
applyToLines "/the/filename.txt" 1000 DoSomething
正如您所看到的,这不是一个简单的转换,如果您想在处理完成后做一些事情,您需要传递回调。您也可以使用globals,但这很难做到优雅

如果您需要帮助更改代码以使其正常工作,则需要向我们显示您需要帮助的代码

在8.6中引入延迟 在TCL8.6中,尽管上述代码技术仍然有效,但您还有另一个选择:协同路由!我们可以这样写:

proc applyToLines {filename limit callback} {
    set f [open $filename]
    for {set i 1} {$i <= $limit} {incr i} {
        set line [gets $f]
        if {[eof $f]} break
        yield [after 10 [info coroutine]]
        $callback $i $line
    }
    close $f
}
coroutine ApplyToAFile applyToLines "/the/filename.txt" 1000 DoSomething
这几乎是相同的,除了带有yield和info-coroutine的行,该行挂起该协程,直到它在大约10毫秒的时间内从事件循环中恢复,以及带有coroutine-ApplyToAFile的行,其中该前缀创建了一个具有给定任意名称ApplyToAFile的协程,并将其设置为运行。正如您所看到的,这样转换代码并不难

协程引擎的后端口完全不可能达到8.5或更高版本;它完全需要8中的非递归脚本执行引擎
.6.

操作系统认为所有文件系统都是“高可用的”,甚至是网络化的。操作系统认为所有文件系统都是“高可用的”,甚至是网络化的。事实证明,在这种情况下,我确实可以对另一个Tcl线程进行异步调用,因为我只需要结果值。然而,我有另一种情况,我在Tcl包装的C函数中加载一个大型数据结构,只在Tcl端公开它的属性和一些操作,我想知道如何异步加载它。我不能使用其他Tcl线程,因为它们不共享内存。可能还有另一个问题。事实证明,在这种情况下,我确实可以对另一个Tcl线程进行异步调用,因为我只需要结果值。然而,我有另一种情况,我在Tcl包装的C函数中加载一个大型数据结构,只在Tcl端公开它的属性和一些操作,我想知道如何异步加载它。我不能使用其他Tcl线程,因为它们不共享内存。可能还有另一个问题。