Цикл со странностью асинхронной операции ожидания

У меня есть следующий прослушиватель клиента, который передает клиента в HandleStationClients. Конструктор HandleStationClients запускает Task с соединением в другом потоке для прослушивания.

Код ниже выполняется в основном потоке с асинхронной функцией. Когда клиент подключается, ожидающая часть ниже продолжится и передаст клиента новым созданным HandleStationClients и перехватит события. Обычно после связывания событий цикл начинается заново и ожидает нового соединения в ожидании. Проблема в том, что этот код зацикливается дважды для каждого соединения. Таким образом, клиент подключается, и HandleStationClients будут созданы, и события будут перехватываться, и цикл while снова запускается, а затем продолжает выполнять тот же процесс, снова создавая новый HandleStationClients и перехватчики событий.

После того, как клиент обработан, ожидающий не ждет, а продолжает работу во второй раз. События запускаются дважды. Я не знаю, что случилось. Кто-нибудь знает?

while (true)
{
    counter += 1;

    // Wait for new connection then do rest
    stationsClientSocket = await stationsServerSocket.AcceptTcpClientAsync();

    stationClients.Add(stationsClientSocket, 0);
    Debug.WriteLine("Client toegevoegd " + counter);

    HandleStationClients stationClient = new HandleStationClients(stationsClientSocket);

    stationClient.ConnectionEstabilished += stationClient_ConnectionEstabilished;
    stationClient.ConnectionClosed += stationClient_ConnectionClosed;
    stationClient.NewDataReceived += stationClient_NewDataReceived;
}

HandleClient выглядит так

class HandleStationClients
{
    public HandleStationClients(TcpClient client)
    {
        Task.Factory.StartNew(() => { ProcessConnection(client); });
    }

    #region Event definitions
    public delegate void NewDataReceivedEventHandler(string newData);
    public event NewDataReceivedEventHandler NewDataReceived;

    public delegate void ConnectionClosedEventHandler();
    public event ConnectionClosedEventHandler ConnectionClosed;

    public delegate void ConnectionEstabilishedEventHandler(IPEndPoint endpoint);
    public event ConnectionEstabilishedEventHandler ConnectionEstabilished;
    #endregion

    public async void ProcessConnection(TcpClient stationsClientSocket)
    {
        byte[] message = new byte[1024];
        int bytesRead;

        NetworkStream networkStream = stationsClientSocket.GetStream();

        if (this.ConnectionEstabilished != null)
        {
            this.ConnectionEstabilished((IPEndPoint)stationsClientSocket.Client.RemoteEndPoint);
        }

        while ((true))
        {
            bytesRead = 0;

            try
            {
                bytesRead = await networkStream.ReadAsync(message, 0, 1024);
            }
            catch (Exception ex)
            {
                // some error hapens here catch it                    
                Debug.WriteLine(ex.Message);
                break;
            }

            if (bytesRead == 0)
            {
                //the client has disconnected from the server
                break;
            }

            ASCIIEncoding encoder = new ASCIIEncoding();

            if (this.NewDataReceived != null)
            {
                byte[] buffer = null;

                string incomingMessage = encoder.GetString(message, 0, bytesRead);

                this.NewDataReceived(incomingMessage);
            }
        }
        stationsClientSocket.Close();
        // Fire the disconnect Event
        this.ConnectionClosed();
    }
}

person Shift    schedule 12.05.2012    source источник
comment
Покажите короткую, но полную программу, демонстрирующую проблему. Слишком сложно понять, что происходит, только по этому фрагменту.   -  person Jon Skeet    schedule 12.05.2012
comment
Я согласен с Джоном. Я попытался запустить короткое консольное приложение, которое делало это, и оно работало нормально.   -  person svick    schedule 12.05.2012
comment
Я не думаю, что есть большая польза от того, чтобы сделать всю задачу асинхронным методом.   -  person Henk Holterman    schedule 12.05.2012
comment
@Shift, тогда ты должен либо сам ответить на этот вопрос, либо, может быть, удалить его.   -  person svick    schedule 12.05.2012


Ответы (2)


Плохая идея запускать задачу в конструкторе. Это означает, что ваша задача выполняется, пока вы регистрируете обработчики событий. Существует неплохая вероятность того, что когда-нибудь вы не зарегистрируете событие до того, как его нужно будет запустить.

Что вам нужно сделать, так это дождаться запуска задачи, пока все обработчики событий не будут зарегистрированы. Вам нужно будет создать метод Start, чтобы позаботиться о запуске задачи, и чтобы код вызывал его после регистрации событий.

Обновленный класс:

class HandleStationClients
{
    // Added a field to store the value until the Start method
    TcpClient _client;

    public HandleStationClients(TcpClient client)
    {
        this._client = client;
        // Moved the line from here...
    }

    public void Start()
    {
        // ...to here.
        Task.Factory.StartNew(() => { ProcessConnection(_client); });
    }

    #region Event definitions
    // ...
    #endregion

    public async void ProcessConnection(TcpClient stationsClientSocket)
    {
        byte[] message = new byte[1024];
        int bytesRead;

        NetworkStream networkStream = stationsClientSocket.GetStream();

        if (this.ConnectionEstabilished != null)
        {
            this.ConnectionEstabilished((IPEndPoint)stationsClientSocket.Client.RemoteEndPoint);
        }

        while ((true))
        {
            bytesRead = 0;

            try
            {
                bytesRead = await networkStream.ReadAsync(message, 0, 1024);
            }
            catch (Exception ex)
            {
                // some error hapens here catch it                    
                Debug.WriteLine(ex.Message);
                break;
            }

            if (bytesRead == 0)
            {
                //the client has disconnected from the server
                break;
            }

            ASCIIEncoding encoder = new ASCIIEncoding();

            if (this.NewDataReceived != null)
            {
                byte[] buffer = null;

                string incomingMessage = encoder.GetString(message, 0, bytesRead);

                this.NewDataReceived(incomingMessage);
            }
        }
        stationsClientSocket.Close();
        // Fire the disconnect Event
        // I added a line to check that ConnectionClosed isn't null
        if (this.ConnectionClosed != null)
        {
            this.ConnectionClosed();
        }
    }
}

Затем вам нужно изменить код вызова следующим образом.

while (true)
{
    counter += 1;

    // Wait for new connection then do rest
    stationsClientSocket = await stationsServerSocket.AcceptTcpClientAsync();

    stationClients.Add(stationsClientSocket, 0);
    Debug.WriteLine("Client toegevoegd " + counter);

    HandleStationClients stationClient = new HandleStationClients(stationsClientSocket);

    stationClient.ConnectionEstabilished += stationClient_ConnectionEstabilished;
    stationClient.ConnectionClosed += stationClient_ConnectionClosed;
    stationClient.NewDataReceived += stationClient_NewDataReceived;
    // Call Start manually
    stationClient.Start();
}
person Kendall Frey    schedule 13.05.2012

Я переместил начало задачи из конструктора в метод Start. Проблема решена.

person Shift    schedule 12.05.2012