【问题标题】:TcpClient Exception DeadlockTcpClient 异常死锁
【发布时间】:2016-04-28 15:24:37
【问题描述】:

我在继承的一些代码中有一个奇怪的行为 - 下面的简化示例演示了如果内置到普通控制台应用程序中的问题。

WhoIs 在第 5 次调用时达到其使用限额 - 并返回一条消息 + 关闭套接字。使用 ReadLineAsync 这会产生一个 SocketException 和一些 IOExceptions - 有时这些被捕获在 catch 块中,一切都应该是,大多数时候它们没有被捕获并且程序只是挂起 - 在 VS 调试器中的 Break all 向我展示了其中一个在主线程上调用 Console.WriteLine。直接在调试器外部运行 .exe 文件时,此行为仍然存在。

任何人都可以看到这是什么/为什么会发生这种情况?

我实际上可以通过使用 Peek() 来解决我的问题,但我想知道在没有被捕获的异常以及“死锁”的情况下发生了什么。大概是某种线程或上下文问题。如果这是我正在做的事情,我想知道什么,以便我可以在其他地方避免它!

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace AsyncIssueConsoleApplication
{
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.WriteLine(Task.Run(() => LookupAsync("elasticsearch.org")).Result);
        Console.ReadLine();
    }

    private static async Task<string> LookupAsync(string domain)
    {
        StringBuilder builder = new StringBuilder();
        TcpClient tcp = new TcpClient();
        await tcp.ConnectAsync("whois.pir.org", 43).ConfigureAwait(false);
        string strDomain = "" + domain + "\r\n";
        byte[] bytDomain = Encoding.ASCII.GetBytes(strDomain.ToCharArray());
        try
        {
            using (Stream s = tcp.GetStream())
            {
                await s.WriteAsync(bytDomain, 0, strDomain.Length).ConfigureAwait(false);
                using (StreamReader sr = new StreamReader(s, Encoding.ASCII))
                {
                    try
                    {
                        //This is fine
                        /*while (sr.Peek() >= 0)
                        {
                            builder.AppendLine(await sr.ReadLineAsync());
                        }*/

                        //This isn't - produces SocketException which usually isn't caught below
                        string strLine = await sr.ReadLineAsync().ConfigureAwait(false);
                        while (null != strLine)
                        {
                            builder.AppendLine(strLine);
                            strLine = await sr.ReadLineAsync().ConfigureAwait(false);
                        }
                    }
                    catch (Exception e)
                    {
                        //Sometimes the SocketException/IOException is caught, sometimes not
                        return builder.ToString();
                    }
                }
            }

        }
        catch (Exception e)
        {
            return builder.ToString();
        }
        return builder.ToString();
    }
}
}

建议的duplicate Q&A 可能相关但不能回答我可以看到的这个查询,当然不能完全回答:即我需要对 SynchronizationContext 做些什么 - 我已经在使用 ConfigureAwait(false)。

当代码如上所述死锁时,堆栈跟踪是:

mscorlib.dll!System.Threading.Monitor.Wait(object obj, int millisecondsTimeout, bool exitContext)   Unknown
mscorlib.dll!System.Threading.Monitor.Wait(object obj, int millisecondsTimeout) Unknown
mscorlib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout = -1, System.Threading.CancellationToken cancellationToken) Unknown
mscorlib.dll!System.Threading.Tasks.Task.SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken)    Unknown
mscorlib.dll!System.Threading.Tasks.Task.InternalWait(int millisecondsTimeout = -1, System.Threading.CancellationToken cancellationToken)   Unknown
mscorlib.dll!System.Threading.Tasks.Task<string>.GetResultCore(bool waitCompletionNotification = true)  Unknown
mscorlib.dll!System.Threading.Tasks.Task<System.__Canon>.Result.get()   Unknown
AsyncIssueConsoleApplication.exe!AsyncIssueConsoleApplication.Program.Main(string[] args = {string[0]}) Line 18 C#

IOException 是:{“无法从传输连接读取数据:现有连接被远程主机强行关闭。”}

SocketException 是:{“已建立的连接被主机中的软件中止”}

【问题讨论】:

  • 处理你的 TCPClient 实例有帮助吗?
  • 我会将ConnectAsync 移动到try-block
  • 你没有处理 TCPClient - 使用 using(TcpClient tcp = new TcpClient()) {...}
  • 删除 catch (Exception) 处理程序,以便异常可以(可能)冒泡到外部,因为当前处理程序除了掩盖所有问题之外什么都不做。或者,指示 VS 中断 throw 异常,而不仅仅是未处理的异常。

标签: c# async-await deadlock tcpclient contextswitchdeadlock


【解决方案1】:

我现在可以重现它。为了查看它挂在哪里,我切换到同步 IO。这是异步 IO 的一个常见调试问题,您无法看到当前待处理的 IO。这是您可能不想首先使用异步 IO 的一个关键原因。

它挂起是因为远程服务器没有关闭连接。 ReadLine 调用只有在远程端关闭连接时才会结束。

这可能是速率限制代码中的错误。这也可能是协议的预期行为。也许您打算现在发送下一个请求?或者也许你应该从上一行检测速率限制并关闭自己。

这不是线程问题。根本没有并发。所有LookupAsync 实例都按顺序运行。

我还尝试正确关闭TcpClients,以防远程服务器在面对多个连接时表现不同。没有效果。不过,无论如何,您都应该处置您的资源。这是严重的泄漏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-24
    • 2020-06-07
    相关资源
    最近更新 更多