SL4 AsyncEventArgs создает исключение InvalidOperationException после второго подключения к сокету - PullRequest
1 голос
/ 03 августа 2010

У меня есть класс, который должен отправлять и получать данные с использованием сокетов в Silverlight 4. Он должен реализовывать уже существующий интерфейс, поэтому некоторые вещи могут выглядеть несколько странно, но вот оно:

public class TcpDataTransportClient : IDataTransportService
{
    private const string TCP_ADDRESS_SETTING = "tcpaddress";
    private const string TCP_PORT_SETTING = "tcpport";

    private static ManualResetEvent clientConnected = new ManualResetEvent(false);
    private static ManualResetEvent clientDataReceived = new ManualResetEvent(false);
    private static ManualResetEvent clientDataSent = new ManualResetEvent(false);

    private Dictionary<string, object> settings = new Dictionary<string, object>();
    private IDataEncapsulator dataEncapsulator;
    private IDataCollector dataCollector;

    private Socket client;
    private SocketAsyncEventArgs clientArgs;

    public event DataReceivedHandler OnDataReceived;
    public event DataSentHandler OnDataSent;

    public TcpDataTransportClient()
    {

    }

    public Dictionary<string, object> Settings
    {
        get
        {
            return this.settings;
        }
        set
        {
            this.settings = value;
        }
    }

    public IDataEncapsulator DataEncapsulator
    {
        get
        {
            return this.dataEncapsulator;
        }
        set
        {
            this.dataEncapsulator = value;
        }
    }

    public void Start(IDataCollector dataCollector)
    {
        this.dataCollector = dataCollector;
        clientArgs = new SocketAsyncEventArgs();

        client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientArgs.Completed += clientArgs_Completed;
        clientArgs.UserToken = client;            
        clientArgs.RemoteEndPoint = GetIPEndPoint();

        client.ConnectAsync(clientArgs);
        clientConnected.WaitOne();          
    }

    private IPEndPoint GetIPEndPoint()
    {
        IPAddress ipAddress;
        int tcpPort;

        if (!IPAddress.TryParse(settings[TCP_ADDRESS_SETTING].ToString(), out ipAddress))
            throw new ArgumentException(String.Format("Invalid setting for IP Address: '{0}'", TCP_ADDRESS_SETTING));

        if (!int.TryParse(settings[TCP_PORT_SETTING].ToString(), out tcpPort))
            throw new ArgumentException(String.Format("Invalid setting for TCP Port: '{0}'", TCP_PORT_SETTING));

        return new IPEndPoint(ipAddress, tcpPort);
    }

    void clientArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Connect:
                ProcessConnect(e);
                break;
            case SocketAsyncOperation.Receive:
                ProcessReceive(e);
                break;
            case SocketAsyncOperation.Send:
                ProcessSend(e);
                break;
            default:
                throw new Exception("Invalid operation completed");
        }
    }

    private void ProcessConnect(SocketAsyncEventArgs e)
    {
        if (e.SocketError != SocketError.Success)
        {
            throw new SocketException((int)e.SocketError);
        }
        else
        {
            clientConnected.Set();
        }
    }

    private void ProcessReceive(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            var socket = e.UserToken as Socket;

            var response = dataCollector.Collect(e.Buffer);

            if (response != null)
            {
                if (this.OnDataReceived != null)
                    this.OnDataReceived(response);

                clientDataReceived.Set();
            }
            else
            {
                bool willRaiseEvent = socket.ReceiveAsync(clientArgs);
                if (!willRaiseEvent)
                    ProcessReceive(e);
            }
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }

    private void ProcessSend(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {                
            var socket = e.UserToken as Socket;

            if (OnDataSent != null)
                OnDataSent(clientArgs.Buffer);

            clientDataSent.Set();
            clientDataReceived.Reset();

            bool willRaiseEvent = socket.ReceiveAsync(e);
            if (!willRaiseEvent)
                ProcessReceive(e);

            clientDataReceived.WaitOne();
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }


    public void Stop()
    {            
        client.Shutdown(SocketShutdown.Send);
        client.Close();
        client.Dispose();
        clientArgs.Dispose();           
    }

    public void Write(byte[] data)
    {
        clientDataSent.Reset();

        clientArgs.SetBuffer(data, 0, data.Length);

        bool willRaiseEvent = client.SendAsync(clientArgs);
        if (!willRaiseEvent)
            ProcessSend(clientArgs);

        clientDataSent.WaitOne();
    }
}

Идея состоит в том, что на каждый запрос (отправка данных) всегда отвечает ответ (прием данных), и он работает нормально, если вы не отключаете и не создаете новое соединение.

Например:

client.Connect();
client.ClearConfiguration(1);
var status = client.RequestStatusDetails(1);
client.Disconnect();

Этот код отправляет несколько запросов и получает ответ на каждый из них.Однако, если вы снова запустите тот же код (или в цикле), соединение будет установлено, но как только код достигнет этой точки:

public void Write(byte[] data)
{
    clientDataSent.Reset();

    clientArgs.SetBuffer(data, 0, data.Length);

    bool willRaiseEvent = client.SendAsync(clientArgs);
    if (!willRaiseEvent)
        ProcessSend(clientArgs);

    clientDataSent.WaitOne();
}

Для client.SendAsync (clientArgs будет сгенерировано исключение));

Это исключение:

Асинхронная операция с сокетом уже выполняется с использованием этого экземпляра SocketAsyncEventArgs

Если, однако, вы устанавливаете точку останова простоперед этим утверждением, пусть VS2010 сломается, затем продолжите отладку, она работает нормально.

Я действительно не могу понять, что является причиной этой проблемы, и нет никакой дополнительной информации.

Любые предложения

Ответы [ 2 ]

1 голос
/ 03 августа 2010

Решил выложить мои комментарии в качестве ответа.

ИМХО Класс AutoResetEvent лучше подходит для ваших нужд.

AutoResetEvent clientDataSent = new AutoResetEvent(true);

public void Write(byte[] data)
{
    // Wait till the Write operation gets a green light to proceed. Consider using a timeout.
    clientDataSent.WaitOne();

    clientArgs.SetBuffer(data, 0, data.Length);

    bool willRaiseEvent = client.SendAsync(clientArgs);

    // Write operation will get a signal either from ProcessSend (sync) or clientArgs_Completed (async),
    if (!willRaiseEvent) ProcessSend(clientArgs);
}

void clientArgs_Completed(object sender, SocketAsyncEventArgs e)
{
    bool throwInvalidOperationException = false;

    switch (e.LastOperation)
    {
        ...
        default:
            throwInvalidOperationException = true;
    }

    //Signal a waiting Write operation that it can proceed.
    clientDataSent.Set();

    if (throwInvalidOperationException) throw new Exception("Invalid operation completed");
}
0 голосов
/ 03 августа 2010

Использование AutoResetEvent, как предложил Ярослав Яндек, похоже, решило мою проблему.Хотя если у кого-то из вас есть предложения по улучшению этого кода, не стесняйтесь делать это.

public class TcpDataTransportClient : IDataTransportService
{
    private const string TCP_ADDRESS_SETTING = "tcpaddress";
    private const string TCP_PORT_SETTING = "tcpport";

    private Dictionary<string, object> settings = new Dictionary<string, object>();
    private IDataEncapsulator dataEncapsulator;
    private IDataCollector dataCollector;

    private Socket client;
    private SocketAsyncEventArgs clientArgs;

    public event DataReceivedHandler OnDataReceived;
    public event DataSentHandler OnDataSent;

    AutoResetEvent clientDataSent = new AutoResetEvent(false);
    AutoResetEvent clientConnected = new AutoResetEvent(false);

    public TcpDataTransportClient()
    {

    }

    public Dictionary<string, object> Settings
    {
        get
        {
            return this.settings;
        }
        set
        {
            this.settings = value;
        }
    }

    public IDataEncapsulator DataEncapsulator
    {
        get
        {
            return this.dataEncapsulator;
        }
        set
        {
            this.dataEncapsulator = value;
        }
    }

    public void Start(IDataCollector dataCollector)
    {
        this.dataCollector = dataCollector;
        clientArgs = new SocketAsyncEventArgs();

        client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientArgs.Completed += clientArgs_Completed;
        clientArgs.UserToken = client;            
        clientArgs.RemoteEndPoint = GetIPEndPoint();

        client.ConnectAsync(clientArgs);
        clientConnected.WaitOne();            
    }

    private IPEndPoint GetIPEndPoint()
    {
        IPAddress ipAddress;
        int tcpPort;

        if (!IPAddress.TryParse(settings[TCP_ADDRESS_SETTING].ToString(), out ipAddress))
            throw new ArgumentException(String.Format("Invalid setting for IP Address: '{0}'", TCP_ADDRESS_SETTING));

        if (!int.TryParse(settings[TCP_PORT_SETTING].ToString(), out tcpPort))
            throw new ArgumentException(String.Format("Invalid setting for TCP Port: '{0}'", TCP_PORT_SETTING));

        return new IPEndPoint(ipAddress, tcpPort);
    }

    void clientArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Connect:
                ProcessConnect(e);
                break;
            case SocketAsyncOperation.Receive:
                ProcessReceive(e);
                break;
            case SocketAsyncOperation.Send:
                ProcessSend(e);
                break;
            default:
                throw new Exception("Invalid operation completed");
        }
    }

    private void ProcessConnect(SocketAsyncEventArgs e)
    {
        if (e.SocketError != SocketError.Success)
        {
            throw new SocketException((int)e.SocketError);
        }
        else
        {
            clientConnected.Set();
        }
    }

    private void ProcessReceive(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            var socket = e.UserToken as Socket;

            var response = dataCollector.Collect(e.Buffer);

            if (response != null)
            {
                if (this.OnDataReceived != null)
                    this.OnDataReceived(response);
            }
            else
            {
                bool willRaiseEvent = socket.ReceiveAsync(clientArgs);
                if (!willRaiseEvent)
                    ProcessReceive(e);
            }
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }

    private void ProcessSend(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {                
            var socket = e.UserToken as Socket;

            if (OnDataSent != null)
                OnDataSent(clientArgs.Buffer);

            bool willRaiseEvent = socket.ReceiveAsync(e);
            if (!willRaiseEvent)
                ProcessReceive(e);

            clientDataSent.Set();
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }


    public void Stop()
    {            
        client.Shutdown(SocketShutdown.Send);
        client.Close();
        client.Dispose();
        clientArgs.Dispose();           
    }

    public void Write(byte[] data)
    {          
        clientArgs.SetBuffer(data, 0, data.Length);

        bool willRaiseEvent = client.SendAsync(clientArgs);
        if (!willRaiseEvent)
            ProcessSend(clientArgs);

        clientDataSent.WaitOne();
    }
}

Теперь я могу отключаться и подключаться столько раз, сколько захочу.Но сначала я вызываю SendAsync (), который, насколько я понимаю, отправит данные в фоновом режиме (большую часть времени), а затем следующий вызов .WaitOne (), где он блокирует поток до тех пор, пока данные не будут фактически отправлены.То же самое касается подключения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...