You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core 6下SSH.NET异步代码同步执行问题排查

问题根因

当前代码无法实现逐行实时输出、异步执行失效,是以下几个明确的逻辑错误导致的:

  • 读取SSH输出使用了ReadToEndAsync()方法,该方法会持续阻塞直到对应流被完全关闭(即SSH命令执行结束、所有输出全部写入完成)才会返回结果,因此你只能在命令执行完成时拿到一次全量输出,不可能在执行过程中逐行推送。
  • 循环中使用Thread.Yield()做等待是无效逻辑,该方法仅会将当前CPU时间片让给同核心的其他就绪线程,不会主动等待SSH输出数据到达,会导致循环空转占满CPU资源,完全起不到异步等待IO的作用。
  • 控制器Action没有正确使用异步等待:直接Return Ok(Server.BashAsync(Model.BashCmd))没有加Await关键字,会导致Action在BashAsync任务还未执行完成时就直接将Task对象序列化返回,请求生命周期提前终止,后续的输出上报逻辑根本无法正常关联到当前请求。
  • 自行创建的CancellationToken是空值,没有绑定任何取消触发源,后续无论是前端中断请求还是Quartz任务终止,都无法正常中断正在执行的SSH命令,会产生资源泄漏。
  • 单循环交替读取标准输出、标准错误流的逻辑存在阻塞问题:如果其中一个流暂时没有数据,会直接卡住另一个流的读取,导致输出延迟甚至丢失。
修正代码

控制器层修正

将Action改为异步方法,正确等待任务执行,绑定请求取消令牌:

Public Async Function AsyncBash(Model As KvmBashRequest) As Task(Of IActionResult)
    ' 绑定请求取消令牌,前端断开连接时自动触发取消
    Dim requestCt = HttpContext.RequestAborted
    Dim execResult = Await Server.BashAsync(Model.BashCmd, requestCt)
    Return Ok(execResult)
End Function

BashAsync方法修正

不要创建空的取消令牌,将Progress实例的上报逻辑和SignalR推送对接:

Public Async Function BashAsync(BashCmd As String, ct As CancellationToken) As Task(Of String)
    If Not SshClient.IsConnected Then
        Return "SSH not connected"
    End If
    Try
        Dim sshCmd = SshClient.CreateCommand(BashCmd)
        ' 进度上报直接对接SignalR推送逻辑
        Dim pushProgress = New Progress(Of SshOutputLine)(Sub(outputLine)
            ' 替换为你自己的SignalR推送逻辑,将outputLine推送到前端
            Debug.WriteLine($"[SSH Output] IsError:{outputLine.IsErrorLine}, Content:{outputLine.Line}")
        End Sub)
        Await sshCmd.ExecuteAsync(pushProgress, ct)
        Return "Command execute success"
    Catch ex As OperationCanceledException
        Return "Command canceled"
    Catch ex As Exception
        Return $"Execute failed: {ex.Message}"
    End Try
End Function

SSH扩展方法修正

替换无效的ReadToEnd和Thread.Yield逻辑,用独立任务分别读取两个输出流,逐行上报:

Imports System.IO
Imports System.Runtime.CompilerServices
Imports System.Threading
Imports Renci.SshNet

Module SshCommandExtensions
    <Extension()>
    Async Function ExecuteAsync(
        sshCmd As SshCommand, 
        outputProgress As IProgress(Of SshOutputLine), 
        ct As CancellationToken) As Task

        ' 注册取消回调,触发时主动终止SSH命令
        Using cancelReg = ct.Register(Sub() sshCmd.CancelAsync())
            Dim execAsyncResult = sshCmd.BeginExecute()
            Using stdoutReader = New StreamReader(sshCmd.OutputStream),
                  stderrReader = New StreamReader(sshCmd.ExtendedOutputStream)
                ' 启动两个独立任务分别读取标准输出、错误输出,避免互相阻塞
                Dim readStdoutTask = ReadLinesToProgressAsync(stdoutReader, False, outputProgress, ct)
                Dim readStderrTask = ReadLinesToProgressAsync(stderrReader, True, outputProgress, ct)

                ' 等待SSH命令执行完成
                Await Task.Factory.FromAsync(execAsyncResult, AddressOf sshCmd.EndExecute)
                ' 等待剩余所有输出读取完成
                Await Task.WhenAll(readStdoutTask, readStderrTask)
            End Using
        End Using
    End Function

    ''' <summary>
    ''' 逐行读取流内容,每读到一行就立刻上报进度
    ''' </summary>
    Private Async Function ReadLinesToProgressAsync(
        reader As TextReader,
        isErrorLine As Boolean,
        progress As IProgress(Of SshOutputLine),
        ct As CancellationToken) As Task

        Dim lineContent As String
        While True
            ct.ThrowIfCancellationRequested()
            lineContent = Await reader.ReadLineAsync()
            ' 流读取到末尾时退出循环
            If lineContent Is Nothing Then Exit While
            If Not String.IsNullOrWhiteSpace(lineContent) Then
                progress.Report(New SshOutputLine(lineContent, isErrorLine))
            End If
        End While
    End Function
End Module
后续对接Quartz与SignalR的注意事项
  • 持续数小时的长任务不要放在HTTP请求线程内执行,即使修正了异步逻辑,请求超时、前端断开连接、应用回收都可能导致任务意外终止。对接Quartz时要将SSH执行逻辑放到Quartz的调度工作线程中,任务启动后立刻返回任务ID,执行过程中通过IHubContext主动向前端推送输出和状态,不依赖HTTP请求上下文。
  • SshClient实例不要跨线程、跨命令共享,避免连接状态错乱、输出串流问题,建议按任务维度创建独立的SSH连接,任务执行完成后主动释放。
  • 长任务需要持久化任务状态、输出日志,前端断线重连时可以根据任务ID拉取历史输出和当前状态,避免消息丢失。

内容的提问来源于stack exchange,提问作者user18928007

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:01:46