SL4 AsyncEventArgs在第二个Socket连接之后抛出InvalidOperationException

时间:2010-08-03 07:44:02

标签: c# silverlight sockets asynchronous

我有一个必须使用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 个答案:

答案 0 :(得分:1)

决定将我的评论作为答案。

恕我直言,AutoResetEvent Class更适合您的需求。

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");
}

答案 1 :(得分:0)

使用AutoResetEvent作为Jaroslav Jandek建议似乎解决了我的问题。 虽然如果您有任何关于如何改进此代码的建议,请随时这样做。

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(),它会阻塞线程直到数据实际发送完毕。连接也是如此。