尽管经过一定数量的消息后仍然有可用数据,但读取流“冻结”

最后发布: 2018-09-03 05:23:38


问题

我正在与Clangd语言服务器通信的C#应用​​程序上工作。 clangd作为一个单独的进程启动,由我的C#程序管理,通过I / O重定向进行通信。

我的程序正在与clangd交换语言服务器协议请求-响应对 请求通过其进程的StandardInput流发送到clangd,并使用其进程的StandardOutput流读取响应。 clangd使用其进程的StandardError流发出调试信息。 我正在使用异步方法进行读写,以保持用户界面的响应速度。

但是,在发送第三条textDocument / didOpen消息后,我的程序在尝试读取响应时冻结。 利用我发现的StandardError流,当发出调试消息时, textDocument/didOpen正确处理第三条textDocument/didOpen消息,这意味着响应应该在StandardOutput上可用。

我将请求保存在文件中,然后将其发送到在命令行上运行的clangd实例,它就像一个超级按钮一样工作。 我附上了该文件,以备您需要。

此外,在SendRequest方法底部读取的调试消息表明该文件已打开:

I[09:55:53.552] <-- textDocument/didOpen(26)
I[09:55:56.512] Updating file C:\Temp\crossrail\src\ClLogic.cpp with command [C:\Temp\crossrail\src] clang C:\Temp\crossrail\src\ClLogic.cpp -resource-dir=C:\Program Files (x86)\LLVM\bin\..\lib\clang\8.0.0

在下面,您可以看到用于读取和写入响应的LSP客户端代码。 我标记了被阻止的位置。

private Process languageServer;
private StreamWriter requestWriter;
private StreamReader responseReader;
private StreamReader errorReader;
// ...

public void Connect(String workingDirectory)
{
    if (this.Connected == false)
    {
        this.currentMessageID = LSP_FIRST_MESSAGE_ID;
        this.languageServer.StartInfo.WorkingDirectory = workingDirectory;
        this.languageServer.Start();
        this.Connected = true;

        this.requestWriter = this.languageServer.StandardInput;
        this.responseReader = this.languageServer.StandardOutput;
        this.errorReader = this.languageServer.StandardError;
    }
}

public async Task<String> Query<T>(JsonRpcRequest<T> request)
{
    await mutex.WaitAsync();
    try
    {
        await this.SendRequest(request);
        return await this.ReadResponse();
    }
    finally
    {
        mutex.Release();
    }
}

private async Task SendRequest<T>(JsonRpcRequest<T> request)
{
    request.ID = this.currentMessageID;
    ++this.currentMessageID;

    String requestBody = request.ToString();
    Console.WriteLine(requestBody);
    await this.requestWriter.WriteAsync(requestBody.ToCharArray(), 0, requestBody.Length);
    await this.requestWriter.FlushAsync();

    if (request.ID == 26) // ID of the third textDocument/didOpen message
    {
        //await this.ReadErrors(); // the debug messages following the third textDocument/didOpen request are printed correctly
    }
}

private async Task<String> ReadResponse()
{
    String contentLengthHeader = await this.responseReader.ReadLineAsync(); // blocks after the third textDocument/didOpen message
    int responseLength = Int32.Parse
    (
        contentLengthHeader.Substring(contentLengthHeader.IndexOf(LSP_HEADER_KEY_VALUE_DELIMITER) + LSP_HEADER_VALUE_OFFSET)
            .Trim()
    );
    await this.responseReader.ReadLineAsync();

    char[] buffer = new char[BUFFER_SIZE];
    StringBuilder response = new StringBuilder();
    int totalReadBytes = 0;
    while (totalReadBytes < responseLength)
    {
        int readBytes = await this.responseReader.ReadAsync(buffer, 0, BUFFER_SIZE);
        response.Append(buffer, 0, readBytes);

        totalReadBytes += readBytes;
    }

    Console.WriteLine(response.ToString());
    return response.ToString();
}

public async Task SendFileCloseMessage(DocumentCloseRequest request)
{
    await mutex.WaitAsync();
    try
    {
        await this.SendRequest(request);
        this.responseReader.DiscardBufferedData();
    }
    finally
    {
        mutex.Release();
    }
}

这是我的代码,使用LSP客户端的方法发送textDocument / didOpen消息:

private async Task InitializeLanguageServer(bool highlightFunctionDeclarations)
{
    if (this.languageServer.Connected == false)
    {
        this.languageServer.Connect(this.workingDirectory);
        await this.SendInitializationMessage();
    }
    await this.SendOpenFileMessage();
    await this.LoadSymbolDeclarationLocations(highlightFunctionDeclarations);
    await this.LoadSymbolUsages();
}

private async Task SendInitializationMessage()
{
    InitializationRequest request = new InitializationRequest
    (
        this.workingDirectory,
        System.Diagnostics.Process.GetCurrentProcess().Id,
        false,
        ApplicationSettings.LANGUAGE_SERVER_PROTOCOL_SUPPORTED_SYMBOLS
    );
    Console.WriteLine(await this.languageServer.Query(request));
}

private async Task SendOpenFileMessage()
{
    DocumentOpenRequest request = new DocumentOpenRequest(this.filePath, "cpp", 1, this.SourceCode);
    Console.WriteLine(await this.languageServer.Query(request));
}

无需 await 即可在构造函数中调用InitializeLanguageServer,但这不成问题,因为clangd的速度足以在最多2.5秒的时间内处理每个源代码文件。 使用TinyIoC检索languageServer成员:

public SourceViewerVM()
{
    // ...
    this.languageServer = TinyIoCContainer.Current.Resolve<LanguageServerProtocolClient>();
    #pragma warning disable CS4014
    this.InitializeLanguageServer(highlightFunctionDeclarations);
    #pragma warning restore CS4014
}

编辑:

阅读确实会阻塞,而不仅仅是等待换行符。 如果将以下代码放在通常读取StandardError的断点处,执行也会被阻塞:

if (request.ID == 26) // 26 is the ID of the third textRequest/didOpen message
{
    char[] buffer = new char[BUFFER_SIZE];
    int readBytes = await this.responseReader.ReadAsync(buffer, 0, BUFFER_SIZE); // blocking
    Console.WriteLine(new String(buffer, 0, readBytes));
    //await this.ReadErrors();
}
c#