2

使用 Async、Task、CancellationToken 等都假定您正在调用的 Async 函数/子有一个循环要做,例如加载图片或从 URL 列表中获取 Web 内容,这很好,因为您是打断 LIST。

但是他们都缺少的是一种取消(Async/Await/Task,或其他)单个调用(代码行)的方法......我的用例是这样的:

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

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

我很喜欢使用 Timer(),但是如何以不完全取消被调用的 Sub/Function 的方式执行此操作?理想情况下,如果我可以在 Try() _ FROM _ Timer中抛出异常,理论上我可以完成一个更有用的实用程序,只需中断一个循环(我可以简单地做一段时间......检查取消如果那是案子...)

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

TIA 为您提供任何想法。

4

2 回答 2

3

您可以通过Task.WhenAnyAwait 执行此操作:

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

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

于 2013-11-05T01:05:30.603 回答
1

关于您的特定 SSH 用例,仍然可以自然地取消挂起的 SSH 请求,具体取决于您用于发出 SSH 命令的方法。例如,SSH.NET 库提供SshCommand.BeginExecute//方法EndExecuteCancelAsync称为异步编程模型 (APM) 模式。像这样的 APM 可以很容易地包装为 await-able 和 cancel-ableTask,使用TaskCompletionSourceand CancellationTokenSource

例如,使用 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.

于 2013-11-05T06:44:42.680 回答