2017-11-15 11 views
-1

免責事項:私のC#は私のC++ほど良くはありませんインターロック== 0で待っていますか?

私のコンポーネントのテストアプリケーションを書くために、C#で非同期ソケットを行う方法を学びたいと思っています。れるtcpClientを使用して私のかつての試みは失敗に終わったとあなたはここでその上の優れた質問を読むことができます:私はその作業を取得できませんでした

TcpClient.NetworkStream Async operations - Canceling/Disconnect

Detect errors with NetworkStream.WriteAsync

、以来、私はSocket.BeginXとソケットを使用してみました代わりに.EndX。私はもっ​​と一緒になった。私の問題は、以下のリストでは、切断する時間が来て、ソケットでシャットダウンとクローズを呼び出すときに、非同期操作がまだ未解決で、オブジェクトが例外をスローしたか、オブジェクトがnull例外に設定されていることです。

は、私がここでそのことについて同様の記事が見つかりました:

After disposing async socket (.Net) callbacks still get called

しかし、私はその答えを受け入れていない、あなたはのための例外を使用している場合は行動を意図しているため、その後、1)彼らは例外ではありません2)ソケット以外の非同期メソッドで破棄されたオブジェクトまたはnull参照を実際に使用したため、例外が意図したとおりにスローされたかどうか、またはスローされたかどうかはわかりません。

非同期ソケットコードのC++では、インターロック付きの未処理の非同期操作の数を追跡しています。切断するときは、シャットダウンを呼び出し、インターロックが0になるのを待ってから私が必要としたメンバーを閉じて破壊する。

すべての未処理の非同期操作がC#で完了するためには、次のリストのDisconnectメソッドでどのように待つことになりますか?メイン

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

using log4net; 
using System.Net.Sockets; 
using System.Net; 

namespace IntegrationTests 
{ 
    public class Client2 
    { 
     class ReceiveContext 
     { 
      public Socket m_socket; 
      public const int m_bufferSize = 1024; 
      public byte[] m_buffer = new byte[m_bufferSize]; 
     } 

     private static readonly ILog log = LogManager.GetLogger("root"); 

     static private ulong m_lastId = 1; 

     private ulong m_id; 
     private string m_host; 
     private uint m_port; 
     private uint m_timeoutMilliseconds; 
     private string m_clientId; 
     private Socket m_socket; 
     private uint m_numOutstandingAsyncOps; 

     public Client2(string host, uint port, string clientId, uint timeoutMilliseconds) 
     { 
      m_id      = m_lastId++; 
      m_host     = host; 
      m_port     = port; 
      m_clientId    = clientId; 
      m_timeoutMilliseconds = timeoutMilliseconds; 
      m_socket     = null; 
      m_numOutstandingAsyncOps = 0; 
     } 

     ~Client2() 
     { 
      Disconnect(); 
     } 

     public void Connect() 
     { 
      IPHostEntry ipHostInfo = Dns.GetHostEntry(m_host); 
      IPAddress[] ipV4Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).ToArray(); 
      IPAddress[] ipV6Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetworkV6).ToArray(); 
      IPEndPoint endpoint = new IPEndPoint(ipV4Addresses[0], (int)m_port); 

      m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
      m_socket.ReceiveTimeout = (int)m_timeoutMilliseconds; 
      m_socket.SendTimeout = (int)m_timeoutMilliseconds; 

      try 
      { 
       m_socket.Connect(endpoint); 

       log.Info(string.Format("Connected to: {0}", m_socket.RemoteEndPoint.ToString())); 

       // Issue the next async receive 
       ReceiveContext context = new ReceiveContext(); 
       context.m_socket = m_socket; 
       m_socket.BeginReceive(context.m_buffer, 0, ReceiveContext.m_bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), context); 
      } 
      catch (Exception e) 
      { 
       // Error 
       log.Error(string.Format("Client #{0} Exception caught OnConnect. Exception: {1}" 
             , m_id, e.ToString())); 
      } 
     } 

     public void Disconnect() 
     { 
      if (m_socket != null) 
      { 
       m_socket.Shutdown(SocketShutdown.Both); 

       // TODO - <--- Error here in the callbacks where they try to use the socket and it is disposed 
       //  We need to wait here until all outstanding async operations complete 
       //  Should we use Interlocked to keep track of them and wait on it somehow? 
       m_socket.Close(); 
       m_socket = null; 
      } 
     } 

     public void Login() 
     { 
      string loginRequest = string.Format("loginstuff{0})", m_clientId); 
      var data = Encoding.ASCII.GetBytes(loginRequest); 

      m_socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), m_socket); 
     } 

     public void MakeRequest(string thingy) 
     { 
      string message = string.Format("requeststuff{0}", thingy); 
      var data = Encoding.ASCII.GetBytes(message); 

      m_socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), m_socket); 
     } 

     void OnReceive(IAsyncResult asyncResult) 
     { 
      ReceiveContext context = (ReceiveContext)asyncResult.AsyncState; 

      string data = null; 
      try 
      { 
       int bytesReceived = context.m_socket.EndReceive(asyncResult); 
       data = Encoding.ASCII.GetString(context.m_buffer, 0, bytesReceived); 

       ReceiveContext newContext = new ReceiveContext(); 
       newContext.m_socket = context.m_socket; 

       m_socket.BeginReceive(newContext.m_buffer, 0, ReceiveContext.m_bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), newContext); 
      } 
      catch(SocketException e) 
      { 
       if(e.SocketErrorCode == SocketError.ConnectionAborted) // Check if we disconnected on our end 
       { 
        return; 
       } 
      } 
      catch (Exception e) 
      { 
       // Error 
       log.Error(string.Format("Client #{0} Exception caught OnReceive. Exception: {1}" 
             , m_id, e.ToString())); 
      } 
     } 

     void OnSend(IAsyncResult asyncResult) 
     { 
      Socket socket = (Socket)asyncResult.AsyncState; 

      try 
      { 
       int bytesSent = socket.EndSend(asyncResult); 
      } 
      catch(Exception e) 
      { 
       log.Error(string.Format("Client #{0} Exception caught OnSend. Exception: {1}" 
             , m_id, e.ToString())); 
      } 
     } 
    } 
} 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

using log4net; 
using log4net.Config; 

namespace IntegrationTests 
{ 
    class Program 
    { 
     private static readonly ILog log = LogManager.GetLogger("root"); 

     static void Main(string[] args) 
     { 
      try 
      { 
       XmlConfigurator.Configure(); 
       log.Info("Starting Component Integration Tests..."); 

       Client2 client = new Client2("127.0.0.1", 24001, "MyClientId", 60000); 
       client.Connect(); 
       client.Login(); 
       client.MakeRequest("StuffAndPuff"); 

       System.Threading.Thread.Sleep(60000); // Sim work until user shutsdown 

       client.Disconnect(); 
      } 
      catch (Exception e) 
      { 
       log.Error(string.Format("Caught an exception in main. Exception: {0}" 
             , e.ToString())); 
      } 
     } 
    } 
} 

EDIT:

ここに私の能力を最大限にEVKによって提案された解答を使用して、私の追加の試みは、あります。私の言う限り、それはうまく動作します。

問題は、カウンタやソケットの状態を変更するものをロックするための要件のため、基本的に同期呼び出しに非同期のすべてを作成したような気がします。繰り返しますが、私はC++に比べてC#の初心者ですので、答えを解釈してマークを完全に忘れてしまった場合は指摘してください。

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Net; 
using System.Net.Sockets; 
using System.Text; 
using System.Threading; 
using System.Threading.Tasks; 

namespace IntegrationTests 
{ 
    public class Client 
    { 
     class ReceiveContext 
     { 
      public const int  _bufferSize = 1024; 
      public byte[]  _buffer  = new byte[_bufferSize]; // Contains bytes from one receive 
      public StringBuilder _stringBuilder = new StringBuilder(); // Contains bytes for multiple receives in order to build message up to delim 
     } 

     private static readonly ILog _log = LogManager.GetLogger("root"); 

     static private ulong _lastId = 1; 
     private ulong _id; 

     protected string   _host; 
     protected int   _port; 
     protected int   _timeoutMilliseconds; 
     protected string   _sessionId; 
     protected Socket   _socket; 
     protected object   _lockNumOutstandingAsyncOps; 
     protected int   _numOutstandingAsyncOps; 
     private bool    _disposed = false; 

     public Client(string host, int port, string sessionId, int timeoutMilliseconds) 
     { 
      _id       = _lastId++; 
      _host      = host; 
      _port      = port; 
      _sessionId     = sessionId; 
      _timeoutMilliseconds  = timeoutMilliseconds; 
      _socket      = null; 
      _numOutstandingAsyncOps  = 0; 
      _lockNumOutstandingAsyncOps = new object(); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      if(_disposed) 
      { 
       return; 
      } 

      if (disposing) 
      { 
       _socket.Close(); 
      } 

      _disposed = true; 
     } 

     public void Connect() 
     { 
      lock (_lockNumOutstandingAsyncOps) 
      { 
       IPHostEntry ipHostInfo = Dns.GetHostEntry(_host); 
       IPAddress[] ipV4Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).ToArray(); 
       IPAddress[] ipV6Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetworkV6).ToArray(); 
       IPEndPoint endpoint = new IPEndPoint(ipV4Addresses[0], _port); 

       _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
       _socket.ReceiveTimeout = _timeoutMilliseconds; 
       _socket.SendTimeout = _timeoutMilliseconds; 

       try 
       { 
        _socket.Connect(endpoint); 
       } 
       catch (Exception e) 
       { 
        // Error 
        Debug.WriteLine(string.Format("Client #{0} Exception caught OnConnect. Exception: {1}" 
              , _id, e.ToString())); 
        return; 
       } 

       Debug.WriteLine(string.Format("Client #{0} connected to: {1}", _id, _socket.RemoteEndPoint.ToString())); 

       // Issue the first async receive 
       ReceiveContext context = new ReceiveContext(); 

       ++_numOutstandingAsyncOps; 
       _socket.BeginReceive(context._buffer, 0, ReceiveContext._bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), context); 
      } 
     } 

     public void Disconnect() 
     { 
      if (_socket != null) 
      { 
       // We need to wait here until all outstanding async operations complete 
       // In order to avoid getting 'Object was disposed' exceptions in those async ops that use the socket 
       lock(_lockNumOutstandingAsyncOps) 
       { 
        Debug.WriteLine(string.Format("Client #{0} Disconnecting...", _id)); 

        _socket.Shutdown(SocketShutdown.Both); 

        while (_numOutstandingAsyncOps > 0) 
        { 
         Monitor.Wait(_lockNumOutstandingAsyncOps); 
        } 

        _socket.Close(); 
        _socket = null; 
       } 
      } 
     } 

     public void Login() 
     { 
      lock (_lockNumOutstandingAsyncOps) 
      { 
       if (_socket != null && _socket.Connected) 
       { 
        string loginRequest = string.Format("loginstuff{0}", _clientId); 
        var data = Encoding.ASCII.GetBytes(loginRequest); 

        Debug.WriteLine(string.Format("Client #{0} Sending Login Request: {1}" 
              , _id, loginRequest)); 

        ++_numOutstandingAsyncOps; 
        _socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), _socket); 
       } 
       else 
       { 
        Debug.WriteLine(string.Format("Client #{0} Login was called, but Socket is null or no longer connected." 
              , _id)); 
       } 
      } 
     } 

     public void MakeRequest(string thingy) 
     { 
      lock (_lockNumOutstandingAsyncOps) 
      { 
       if (_socket != null && _socket.Connected) 
       { 
        string message = string.Format("requeststuff{0}", thingy); 
        var data = Encoding.ASCII.GetBytes(message); 

        Debug.WriteLine(string.Format("Client #{0} Sending Request: {1}" 
              , _id, message)); 

        ++_numOutstandingAsyncOps; 
        _socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), _socket); 
       } 
       else 
       { 
        Debug.WriteLine(string.Format("Client #{0} MakeRequest was called, but Socket is null or no longer connected." 
              , _id)); 
       } 
      } 
     } 

     protected void OnReceive(IAsyncResult asyncResult) 
     { 
      lock (_lockNumOutstandingAsyncOps) 
      { 
       ReceiveContext context = (ReceiveContext)asyncResult.AsyncState; 

       string data = null; 

       try 
       { 
        int bytesReceived = _socket.EndReceive(asyncResult); 
        data = Encoding.ASCII.GetString(context._buffer, 0, bytesReceived); 

        // If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received, 
        // the EndReceive method will complete immediately and return zero bytes 
        if (bytesReceived > 0) 
        { 
         StringBuilder stringBuilder = context._stringBuilder.Append(data); 

         int index = -1; 
         do 
         { 
          index = stringBuilder.ToString().IndexOf("#"); 
          if (index != -1) 
          { 
           string message = stringBuilder.ToString().Substring(0, index + 1); 
           stringBuilder.Remove(0, index + 1); 

           Debug.WriteLine(string.Format("Client #{0} Received Data: {1}" 
                 , _id, message)); 
          } 
         } while (index != -1); 
        } 
       } 
       catch (SocketException e) 
       { 
        // Check if we disconnected on our end 
        if (e.SocketErrorCode == SocketError.ConnectionAborted) 
        { 
         // Ignore 
        } 
        else 
        { 
         // Error 
         Debug.WriteLine(string.Format("Client #{0} SocketException caught OnReceive. Exception: {1}" 
               , _id, e.ToString())); 
         Disconnect(); 
        } 
       } 
       catch (Exception e) 
       { 
        // Error 
        Debug.WriteLine(string.Format("Client #{0} Exception caught OnReceive. Exception: {1}" 
              , _id, e.ToString())); 
        Disconnect(); 
       } 
       finally 
       { 
        --_numOutstandingAsyncOps; 
        Monitor.Pulse(_lockNumOutstandingAsyncOps); 
       } 
      } 

      // Issue the next async receive 
      lock (_lockNumOutstandingAsyncOps) 
      { 
       if (_socket != null && _socket.Connected) 
       { 
        ++_numOutstandingAsyncOps; 

        ReceiveContext newContext = new ReceiveContext(); 
        _socket.BeginReceive(newContext._buffer, 0, ReceiveContext._bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), newContext); 
       } 
      } 
     } 

     protected void OnSend(IAsyncResult asyncResult) 
     { 
      lock (_lockNumOutstandingAsyncOps) 
      { 
       try 
       { 
        int bytesSent = _socket.EndSend(asyncResult); 
       } 
       catch (Exception e) 
       { 
        Debug.WriteLine(string.Format("Client #{0} Exception caught OnSend. Exception: {1}" 
              , _id, e.ToString())); 
        Disconnect(); 
       } 
       finally 
       { 
        --_numOutstandingAsyncOps; 
        Monitor.Pulse(_lockNumOutstandingAsyncOps); 
       } 
      } 
     } 
    } 
} 
+2

注意、管理されていないリソースを。あなたはあなたのオブジェクト上にファイナライザを持ってはいけません。 – Servy

+0

['CountdownEvent'](https://docs.microsoft.com/dotnet/api/system.threading.countdownevent)はあなたの友人です。 –

+1

C + +のバックグラウンドを持っているので、C + +のデストラクタと管理されたファイナライザを混同しないでください。C#が構文を借用していて、いくつかの文書で "destructor"という単語を使用しているという事実は、実際には非常に残念です。 'IDisposable'はC#が決定的なクリーンアップに使用するもので、ファイナライザはほとんど必要ありません(オブジェクトがガベージコレクションされたときにアンマネージリソースを解放するための最終的な隙間です - 通常、そのようなオブジェクトは既に' Dispose'ファイナライザは抑制されます)。 –

答えて

1

あなたはそのためのMonitor.WaitMonitor.Pulseを使用することができますファイナライザがアンマネージリソースをクリーンアップするためのものであることを

static int _outstandingOperations; 
static readonly object _lock = new object(); 
static void Main() { 
    for (int i = 0; i < 100; i++) { 
     var tmp = i; 
     Task.Run(() => 
     { 
      lock (_lock) { 
       _outstandingOperations++; 
      } 
      // some work 
      Thread.Sleep(new Random(tmp).Next(0, 5000)); 
      lock (_lock) { 
       _outstandingOperations--; 
       // notify condition might have changed 
       Monitor.Pulse(_lock); 
      } 
     }); 
    } 

    lock (_lock) { 
     // condition check 
     while (_outstandingOperations > 0) 
      // will wait here until pulsed, lock will be released during wait 
      Monitor.Wait(_lock); 
    } 
} 
+0

これに関する質問とそれに関連する質問で元の投稿を編集しようとしています。 –

+0

@ChristopherPiszあなたの例のように、全体的な操作をロックする必要はありません。私の答えのように、増分/減分フィールドの周りでのみ使用してください。それを別々のメソッド(IncAnynsOperations/DecAsyncOperations)に移動することができます。 – Evk

+0

非同期メソッドはまだソケット上で動作していたので、ソケットの状態もロックする必要があったため、切断が呼び出されたときにカウントの増減分だけロックされ、同じ古い例外が発生しました。 –

関連する問題