使用Async,Task,CancelToken等都假设您正在调用的Async function/sub有一个循环要做的事情,例如加载图片或从URL列表中获取Web内容,这很好,因为您正在中断LIST。
但是他们都缺少一种取消(异步/等待/任务或其他)单个调用(代码行)的方法......我的用例是这样的:
- 在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
,使用TaskCompletionSource
和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
时引发异常。