2010-08-03 63 views
1

我有一個類必須使用Silverlight 4中的套接字發送和接收數據。 它必須實現預先存在的接口,所以有些東西可能看起來有點奇怪,但 那就是:SL4 AsyncEventArgs在第二個Socket連接後拋出InvalidOperationException異常

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突破進展在它上面,然後繼續調試它工作正常。

我真的不知道是什麼原因導致此問題, 並沒有任何其他信息。

有什麼建議嗎?

+0

你有一些野生鎖定那裏。您的代碼不允許同時發送多個請求。禁止它或爲每個異步請求創建一個新的'SocketAsyncEventArgs'實例。 – 2010-08-03 08:11:13

+0

哦......它真的不得不禁止多個請求,你能指出我做錯了什麼嗎? – TimothyP 2010-08-03 08:49:42

+0

@TimothyP:在調用client.SendAsync之前,需要使用'clientDataSent.WaitOne',當操作完成時,使用'clientDataSent.Set()'。另外,使用'AutoResetEvent'而不是'ManualResetEvent'並考慮在操作完全沒有完成的情況下使用等待超時。 – 2010-08-03 09:08:40

回答

0

使用的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(),它會阻塞線程直到數據實際發送完畢。連接也是如此。

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

對不起,我必須缺少一些東西。 clientDataSent初始狀態設置爲True,這意味着.WaitOne()不會阻塞,所以在這個例子中它沒有做太多...(我猜) – TimothyP 2010-08-03 10:10:30

+0

大聲笑,我還沒有得到它。不應該initialState是真實的和.WaitOne()來SendAsync後? – TimothyP 2010-08-03 10:11:59

+0

+1,因爲它幫助我解決了這個問題,雖然調用的順序是不同的(可以隨意評論) – TimothyP 2010-08-03 10:25:26

相關問題