如何取消/超时函数/子而不是代码行循环?(vb.net 或Visual Basic)



使用Async,Task,CancelToken等都假设您正在调用的Async function/sub有一个循环要做的事情,例如加载图片或从URL列表中获取Web内容,这很好,因为您正在中断LIST。

但是他们都缺少一种取消(异步/等待/任务或其他)单个调用(代码行)的方法......我的用例是这样的:

  1. 在SSH服务器上执行命令,等待响应。

简单。但是,如果对服务器的调用失败,或者服务器花费的时间太长,或者其他什么,我的控制台应用程序就在那里。我希望它在操作系统外的任务计划程序中运行,我可以创建错误代码/错误级别,但我挂在单行/调用上,而不是在异步子/函数中要做的事情循环中中断一长串。

我对使用 Timer() 很酷,但是如何以不完全取消调用的子/函数的方式执行此操作?理想情况下,如果我可以在计时器的 Try() _FROM_ 中抛出异常,理论上我可以完成一个更有用的实用程序,只需中断循环(我可以简单地做一段时间......如果是这种情况,请检查取消...

我是否以可以理解的方式解释这一点?

TIA为您的任何想法。

您可以通过

Await 的 Task.WhenAny 执行此操作:

Dim theTask = ExecuteSshCommandAsync() ' Start your operation
Dim timeout = Task.Delay(5000) ' Some timeout
Dim first = Await Task.WhenAny(timeout, theTask)
If (first = theTask) Then
    Dim result = theTask.Result
    ' Use results
Else
    ' You timed out here...
End If

请注意,这不会取消或超时实际请求 - 它允许您超时并取消等待请求的时间。 取消正在进行的请求的唯一方法是请求是否确实提供了超时或取消机制。 话虽如此,这通常是"足够好"的,并给出了正确的行为。

关于您的特定 SSH 用例,仍然可以自然地取消挂起的 SSH 请求,具体取决于您使用什么方法来发出 SSH 命令。例如,SSH.NET 库提供SshCommand.BeginExecute/EndExecute/CancelAsync方法,称为异步编程模型(APM)模式。像这样的APM可以很容易地包装为一个可等待和可取消的Task,使用TaskCompletionSourceCancellationTokenSource

例如,使用 SSH.NET 执行异步 SSH 命令可能如下所示:

Imports System.Threading
Imports System.Threading.Tasks
Imports Renci.SshNet
Module Module1
    Async Function ExecSshCommandAsync(command As Renci.SshNet.SshCommand, ct As CancellationToken) As Task(Of String)
        Dim tcs As TaskCompletionSource(Of String) = New TaskCompletionSource(Of String)
        Dim doCancel As Action = Sub()
                                     command.CancelAsync()
                                     tcs.TrySetCanceled()
                                 End Sub
        Using ct.Register(doCancel)
            Dim asyncCallback As System.AsyncCallback = Sub(iar As IAsyncResult) tcs.TrySetResult(command.EndExecute(iar))
            command.BeginExecute(asyncCallback)
            Await tcs.Task
        End Using
        Return tcs.Task.Result
    End Function

    Sub Main()
        Dim command As Renci.SshNet.SshCommand
        ' Initialze the SSH session etc
        ' signal cancellation in 10 sec
        Dim cts As CancellationTokenSource = New CancellationTokenSource(10000)
        ' Blocking wait for result with 10 sec timeout
        Dim Result = ExecSshCommandAsync(command, cts.Token).Result
        Console.WriteLine(Result) 
        ' If Main was async too, we could await ExecSshCommandAsync here:  
        ' Dim result = Await ExecSshCommandAsync(command, cts.Token) 
    End Sub
End Module

如果使用单独的控制台进程(如 Putty)执行 SSH 命令,则仍然可以使用几乎相同的技术。它将异步读取和解析子进程的控制台输出,并注册一个取消例程,该例程将使用Process.Kill终止进程(或者做一些更好的事情,例如GenerateConsoleCtrlEvent终止它,更多信息)。

此外,如果您只对子SSH进程的退出代码感兴趣,还有另一种方法。您可以将Process.Handle变成可等待的任务,您可以以类似的方式等待结果。取消回调(通过CancellationToken.Register注册)将终止进程,并使任务取消。或者该过程可以自然完成。在这两种情况下,任务都将达到已完成状态,异步等待将结束。

请记住,调用TaskCompletionSource.TrySetCanceled将导致在您await TaskCompletionSource.Task时引发异常。

最新更新