我在我继承的一些代码中有一种奇怪的行为 - 下面的简化示例演示了如果内置到普通的控制台应用程序中的问题。
WhoIs在第5次呼叫时命中其使用限额 - 并返回消息+关闭套接字。使用ReadLineAsync会产生一个SocketException和一些IOExceptions - 有时它们会在catch块中被捕获,一切都是应该的,大多数时候它们都没有被捕获而且程序只是挂起 - 在VS调试器中断开所有显示我的其中一个在主线程上调用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是:{“已建立的连接被主机中的软件中止”}
答案 0 :(得分:1)
我现在可以重现它。为了看到它挂在哪里,我切换到同步IO。对于异步IO,这是一个常见的调试问题,您无法看到当前正在等待的IO。这是您可能不想首先使用异步IO的关键原因。
它挂起,因为远程服务器没有关闭连接。仅当远程端关闭连接时,ReadLine
呼叫才会结束。
这可能是速率限制代码中的错误。它也可能是协议的预期行为。也许你打算现在发送下一个请求?或者也许您应该检测前一行的速率限制并自行关闭。
这不是线程问题。根本没有并发性。所有LookupAsync
实例都按顺序运行。
我还尝试正确关闭TcpClient
,以防远程服务器在多个连接面前表现不同。没有效果。但是,您应该在任何情况下处置您的资源。这是严重的泄漏。