TcpClient Exception Deadlock

У меня есть странное поведение в некотором коде, который я унаследовал - упрощенный пример ниже, который демонстрирует проблему, если встроен в простое консольное приложение.

WhoIs получает разрешение на использование при 5-м вызове и возвращает сообщение + закрывает сокет. При использовании ReadLineAsync это создает SocketException и некоторые IOException - иногда они перехватываются в блоке catch, и все происходит так, как должно быть, в большинстве случаев они не перехватываются, а программа просто зависает - Break all в 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();
    }
}
}

Предлагаемые повторяющиеся вопросы и ответы могут относиться, но не отвечают на этот запрос, который я вижу, конечно, не полностью: то есть, что мне нужно сделать с 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: {"Установленное соединение было прервано программным обеспечением на вашем хост-компьютере"}

1 ответ

Решение

Я могу воспроизвести это сейчас. Чтобы увидеть, где он висит, я переключился на синхронный ввод-вывод. Это общая проблема отладки асинхронного ввода-вывода, когда вы не видите, какие ожидающие ввода-вывода ожидают в данный момент. Это основная причина, по которой вы, возможно, не захотите использовать асинхронный ввод-вывод.

введите описание изображения здесь

введите описание изображения здесь

Он зависает, потому что удаленный сервер не закрывает соединение. ReadLine вызов закончится, только если удаленная сторона закроет соединение.

Это может быть ошибка в коде ограничения скорости. Это также может быть ожидаемое поведение протокола. Может быть, вы должны отправить следующий запрос сейчас? Или, может быть, вы должны обнаружить ограничение скорости из предыдущей строки и отключиться самостоятельно.

Это не проблема с потоками. Там нет параллелизма вообще не происходит. Все LookupAsync экземпляры работают последовательно.

Я также попытался правильно закрыть TcpClientВ случае, если удаленный сервер ведет себя по-разному в случае нескольких подключений. Там не было никакого эффекта. Вы должны распоряжаться своими ресурсами в любом случае, однако. Это серьезная утечка.

Другие вопросы по тегам