none
await - async - Thread; Task RRS feed

  • Frage

  • Hallo,
    ich hoffe, ich darf nun gezielt nachfragen.
    Verwende ich async und await, werden alle Funktionen schön abgearbeitet.
    Die Anwendung hängt nicht.
    Alles soweit gut.
    Die Fragen
      A) Sehe ich das richtig mit dem await, async ?
      B) Wie wandle ich die ersten 4 Bytes in eine Zahl um, die ich empfange?
      C) Wie wandle ich die Länge eines Strings in 4 Bytes um, für das Senden?
      D) Benötige ich im Server noch einen Thread?
     
    Um meine Fragen zu verstehen, dient der untere Code.
    Herzlichen Dank im Voraus für Eure Unterstützung und Tipps.
    Viele Grüße Martin

    Siehe Kommentar.
    
    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                StartListener();           // ### Start
                ConnectAsTcpClient();      // ### nahezu zeitgleich aufgerufen
                Console.ReadLine();
            }
    
            private static async void ConnectAsTcpClient()
            {
                using (var tcpClient = new TcpClient())
                {
                    Console.WriteLine("[Client] Connecting to server");
                    await tcpClient.ConnectAsync("127.0.0.1", 1234);  // ## hier wird gewartet
    				                                  // ## bis es zur Verbindung kommt
    																  // ## aber nicht blockierend!
    																  // ## man merkt es nicht.
    																  // ##
                    Console.WriteLine("[Client] Connected to server");
                    using (var networkStream = tcpClient.GetStream())
                    {
                        Console.WriteLine("[Client] Writing request {0}", ClientRequestString);
                        await networkStream.WriteAsync(ClientRequestBytes, 0, ClientRequestBytes.Length);
            
                        var buffer = new byte[4096];
                        var byteCount = await networkStream.ReadAsync(buffer, 0, buffer.Length);
    					                        // ## hier müsste ich eine Schleife machen,
    											// ## bis die Anzalh der zu erwartenden Bytes zurückkommt.
    											
                        var response = Encoding.UTF8.GetString(buffer, 0, byteCount);
                        Console.WriteLine("[Client] Server response was {0}", response);
                    }
                }
            }
    
            private static readonly string ClientRequestString = "Some HTTP request here";
            private static readonly byte[] ClientRequestBytes = Encoding.UTF8.GetBytes(ClientRequestString);
    
            private static readonly string ServerResponseString = "<?xml version=\"1.0\" encoding=\"utf-8\"?><document><userkey>key</userkey> <machinemode>1</machinemode><serial>0000</serial><unitname>Device</unitname><version>1</version></document>\n";
            private static readonly byte[] ServerResponseBytes = Encoding.UTF8.GetBytes(ServerResponseString);
    
            private static async void StartListener()
            {
                var tcpListener = TcpListener.Create(1234);
                tcpListener.Start();
                var tcpClient = await tcpListener.AcceptTcpClientAsync();
                Console.WriteLine("[Server] Client has connected");
                using (var networkStream = tcpClient.GetStream())
                {
                    var buffer = new byte[4096];
                    Console.WriteLine("[Server] Reading from client");
                    var byteCount = await networkStream.ReadAsync(buffer, 0, buffer.Length);
    				                   // ## hier müsste ich eine Schleife machen,
    											// ## bis die Anzahl der zu erwartenden Bytes zurückkommt.
    											
                    var request = Encoding.UTF8.GetString(buffer, 0, byteCount);
                    Console.WriteLine("[Server] Client wrote {0}", request);
                    await networkStream.WriteAsync(ServerResponseBytes, 0, ServerResponseBytes.Length);
                    Console.WriteLine("[Server] Response has been written");
                }
            }
        }
    }
    
    Ich möchte ja nicht immmer neu verbinden.
    Benötige ich dann noch einen Thread für den Empfang?
    
    D.h. ich rufe die Read Funktion auf und die pollt solange die Applikation läuft.
    
    private static async void Read()
    {
    	Console.WriteLine("[Server] is ready to for receive messages");
    	using (var networkStream = tcpClient.GetStream())
    	{
    	    while(1)
    		{
    		// ##### Abbruch wie?
    			string request = "";
    			while (bytesMustBe < 287)
    			  // #### oder besser die ersten 4 Bytes geben die Länge vor!
    			  // #### wie wandle ich die ersten 4 Bytes in eine Länge um ?
    			{
    				var byteCount = await networkStream.ReadAsync(buffer, 0, buffer.Length);
    											// ## hier müsste ich eine Schleife machen,
    											// ## bis die Anzahl der zu erwartenden Bytes zurückkommt.
    				request += Encoding.UTF8.GetString(buffer, 0, byteCount);
    			}
    			
    			Console.WriteLine("[Server] Client wrote {0}", request);
    			
    			switch (request.Substring(0,10))
    			{
    				case "Workflow1"
    				
    				   ServerResponseBytes = "W1" + ServerResponseBytes;
    				   lengthAnswer = ServerResponseBytes.Length;
    				   ServerResponseBytes = <LängeMessage> + ServerResponseBytes;
    				   
    				break;
    				case "Workflow2"
    				   ServerResponseBytes = "W2" + ServerResponseBytes;
    				break;
    			}
    			
    			await networkStream.WriteAsync(ServerResponseBytes, 0, ServerResponseBytes.Length);
    			Console.WriteLine("[Server] Response has been written");
    		}
    	}
    }
    
    Freitag, 18. November 2016 16:24

Antworten

  • Hallo Martin,

    ich kann das in Code wiedergeben, ich dachte nur, dass es nicht notwendig wäre. Eine von mehreren Möglichkeiten, gekapselt in eigene Methoden, vollkommen testbefreit:

            internal async Task<string> ReadStringBlock(Stream netStream, int length)
            {
                var buffer = await ReadBlock(netStream, length);
                return Encoding.UTF8.GetString(buffer);
            }
    
            internal async Task<byte[]> ReadBlock(Stream netStream, int length)
            {
                byte[] buffer = new byte[length];
    
                int readOffset = 0;
                int readCount = length;
                while (readOffset < length)
                {
                    int resultLength = await netStream.ReadAsync(buffer, readOffset, readCount);
                    if (resultLength <= 0)
                        throw new InvalidDataException("ReadAsync failed");
                    readCount -= resultLength;
                    readOffset += resultLength;
                }
                return buffer;
            }
    

    Gruß Elmar

    Mittwoch, 23. November 2016 10:48
    Beantworter

Alle Antworten

  • Hallo Martin,

    wenn Du die Länge (als Integer) senden bzw. empfangen willst, konvertiere sie über die BitConverter Klasse, die stellt Methoden zum Konvertieren in beide Richtungen bereit.

    Wobei dies nur funktioniert, wenn alle Partien die gleiche Byte-Order verwenden. Im Netzwerk verwendet man allgemein Big Endian, wohingegen Intel-Systeme in der Regel Little Endian verwenden. Wenn man das "richtig" machen möchte, siehe http://stackoverflow.com/questions/3294659/c-sharp-big-endian-ulong-from-4-bytes - wobei Jon Skeets MiscUtils die meisten Möglichkeiten bieten.

    Gruß Elmar

    Freitag, 18. November 2016 17:53
    Beantworter
  • Hallo Martin,

    was Elmar zum Bite converter sagt ist vollkommen richtig. Ich wollte dir noch ein wenig Input zu async await geben. Als erstes solltest du den Rückgabewert von void auf Task ändern. Das sollte man bei async Methoden generell machen, damit man diese auch awaiten kann. (Was keine Funktionelle Änderung ist aber das Codieren einfacher macht wäre noch das deine async Methoden im Namen Async haben, so erkennt man später besser dass diese async sind)

    Um auf den Client zu warten kannst du es so machen wie du es machst. Wobei nur der erste Client der sich verbindet bedient wird. Wenn du mehr als einen Client bedienen willst solltest du wirklich noch einen Thread oder besser Task für den Server machen.

    Deine Main Methode wartet aber nicht auf die aufgerufenen Async Methoden. Wobei du ja mit dem ReadLine blockierst aber wenn nicht könntest du hier auf die Tasks der Async Methoden warten.

    Grüße,

    Daniel

    Montag, 21. November 2016 11:20
  • Hallo Daniel,
    Danke für den Hinweis.
    Wenn Du noch ein konkretes Beispiel hast wäre es toll.

    Du meinst das da?

    static void Main(string[] args)
    {
    	StartListener();           // ### Start
    	ConnectAsTcpClient();      // ### nahezu zeitgleich aufgerufen
    	Console.ReadLine();
    }
    So ganz klar ist mir die Sache doch noch nicht.

    A) Empfange ich in den ersten 4 Bytes die Länge.
    B) Können die Messages ja zerstückelt kommen.
     B1) Also muss ich erst die Länge abfragen, dann die Messages
     B2) <Länge4Bytes><Message...........500 Zeichen><<Länge4Bytes><Message 2 ....>
     B3) Rest von der Message2
    C) Wie würde man das denn richtigerweise lösen?

    Vielen Dank im Voraus.

    Grüße Martin


    private async Task Read()
    {
    	Console.WriteLine("[Server] is ready to for receive messages");
    
    	byte[] dataLength = new byte[4];
    	int toReceived = 0;
    	byte[] buffer = new byte[4096];
    	int toReceivedMessageLength = 0;
    
    	bool startMessage = true;
    	using (var networkStream = tcpClient.GetStream())
    	{
    
    		Array.Clear(dataLength, 0, dataLength.Length);
    		toReceived = 0;
    		Array.Clear(buffer, 0, buffer.Length);
    
    		while(1)
    		{
    			string request = "";
    
    			var byteCount = await networkStream.ReadAsync(buffer, 0, buffer.Length);
    			
    			Array.Resize(ref buffer, byteCount);  // ????
    
    			if ( byteCount > 4 && startMessage == true )
    			{
    				startMessage = false;
    				Buffer.BlockCopy(buffer, 0, dataLength, 0, 4);
    			}
    			else if (startMessage == true )
    			{
    				 Buffer.BlockCopy(buffer, toReceived, dataLength, toReceived, byteCount.Length - toReceived);
    				 toReceived += byteCount;
    				 if ( toReceived >= 4 )   // Länge der zu lesenden Zeichen bekannt.
    					 startMessage = false;
    			}
    			
    			if ( startMessage == false )
    			{
    			    // Array.Copy(Buffer, ibuffer, Buffer, 0, bufferRec.Length - ibuffer);
    				
    				 toReceivedMessageLength = BitConverter.ToInt32(dataLength, 0);
    				 request += Encoding.UTF8.GetString(buffer, 0, byteCount);
    			}
    			
    			switch (request)
    			{
    				case MessageLength:
    				   ....
    					break;
    			   
    				case Message:
    				   ....
    					break;
    				default:
    				   error = -1;
    				    break;
    			}
    
    			Console.WriteLine("[Server] Client wrote {0}", request);
    
    			switch (request.Substring(0,10))
    			{
    				case "Workflow1";
    				   ServerResponseBytes = "W1" + ServerResponseBytes;
    				   lengthAnswer = ServerResponseBytes.Length;
    				   ServerResponseBytes = <lengthAnswer> + ServerResponseBytes;
    				break;
    				case "Workflow2"
    				   ServerResponseBytes = "W2" + ServerResponseBytes;
    				   lengthAnswer = ServerResponseBytes.Length;
    				   ServerResponseBytes = <lengthAnswer> + ServerResponseBytes;
    				break;
    			}
    			await networkStream.WriteAsync(ServerResponseBytes, 0, ServerResponseBytes.Length);
    			Console.WriteLine("[Server] Response has been written");
    		}
    	}
    }

    Montag, 21. November 2016 17:25
  • Hallo Martin,

    also zum Abbrechen solltest du einen CancelationToken verwenden. Server Code und Main könnte dann in etwa so aussehen:

    static void Main(string[] args)
                {
                    CancellationTokenSource cancelToken = new CancellationTokenSource();// will be used to cancel the call
                    Task tServer = StartListener(cancelToken);           // ### Start
                    Task tClient = ConnectAsTcpClient();      // ### nahezu zeitgleich aufgerufen
                    tClient.Wait();//wait for execution
                    Console.WriteLine("Press enter to end server");
                    Console.ReadLine();
                    cancelToken.Cancel(false);
                    Console.WriteLine("Press enter to end exit");
                    tServer.Wait();
                    Console.ReadLine();
                }
    
    private static async Task StartListener(CancellationTokenSource cancelToken)
                {
                    var tcpListener = TcpListener.Create(1234);
                    tcpListener.Start();
                    try
                    {
                        while (!cancelToken.Token.IsCancellationRequested)
                        {
                            //await with cancel
                            var tcpClient = await tcpListener.AcceptTcpClientAsync().WithWaitCancellation(cancelToken.Token);
                           
                            Console.WriteLine("[Server] Client has connected");
                            using (var networkStream = tcpClient.GetStream())
                            {
                                var buffer = new byte[4096];
                                Console.WriteLine("[Server] Reading from client");
                                var byteCount = await networkStream.ReadAsync(buffer, 0, buffer.Length);
                                // ## hier müsste ich eine Schleife machen,
                                // ## bis die Anzahl der zu erwartenden Bytes zurückkommt.
    
                                var request = Encoding.UTF8.GetString(buffer, 0, byteCount);
                                Console.WriteLine("[Server] Client wrote {0}", request);
                                await networkStream.WriteAsync(ServerResponseBytes, 0, ServerResponseBytes.Length);
                                Console.WriteLine("[Server] Response has been written");
                            }
                        }
                    }
                    catch (OperationCanceledException)
                    {
                    Console.WriteLine("[Server] canceled by user");
                } finally 
                    {
                        tcpListener.Stop();
                        Console.WriteLine("[Server] finished");
                    }
                    
                }

    WithWaitCancellation mit Erklärung dazu findest du hier: http://stackoverflow.com/questions/14524209/what-is-the-correct-way-to-cancel-an-async-operation-that-doesnt-accept-a-cance

    Grüße,

    Daniel

    Dienstag, 22. November 2016 07:04
  • Hallo Daniel,
    Danke für den Hinweis.
    Wenn Du noch ein konkretes Beispiel hast wäre es toll.
    Du meinst das da?
    static void Main(string[] args)
    {
     StartListener();           // ### Start
     ConnectAsTcpClient();      // ### nahezu zeitgleich aufgerufen
     Console.ReadLine();
    }
    So ganz klar ist mir die Sache doch noch nicht.

    A) Empfange ich in den ersten 4 Bytes die Länge.
    B) Können die Messages ja zerstückelt kommen.
     B1) Also muss ich erst die Länge abfragen, dann die Messages
     B2) <Länge4Bytes><Message...........500 Zeichen><<Länge4Bytes><Message 2 ....>
     B3) Rest von der Message2test
    C) Wie würde man das denn richtigerweise lösen?

    Ringbuffer? hast da ein Beispiel

    Vielen Dank im Voraus.
    Grüße Martin

    • Bearbeitet Martin Scheid Dienstag, 22. November 2016 18:06 Korrektur
    Dienstag, 22. November 2016 18:04
  • Hallo Martin,

    was Daniel angedeutet hat mit:

    // ## hier müsste ich eine Schleife machen,
    // ## bis die Anzahl der zu erwartenden Bytes zurückkommt.
    

    Read[Async] müsste solange ausgeführt werden, bis die erwartete Anzahl gelesen wurde. Wird vorzeitig "0" geliefert, so ist nicht alles übertragen und die Verbindung von der Gegenstelle geschlossen worden. Sind die Meldungen größer, kann man die Daten zunächst in einen MemoryStream schieben.

    Auch sollte man ggf. mit Send-/Receive Timeout arbeiten, um nicht dauerhaft in der Schleife festzustecken.

    Zuletzt: Man sollte nur die komplette Byte-Folge in eine Zeichenkette umwandeln, denn in UTF-8 kann ein Zeichen mehrere Bytes (1..4) umfassen. Steckt man mitten in einem Zeichen geht die Umwandlung schief.

    Gruß Elmar

    Dienstag, 22. November 2016 19:10
    Beantworter
  • Read[Async] müsste solange ausgeführt werden, bis die erwartete Anzahl gelesen wurde. Wird vorzeitig "0" geliefert, so ist nicht alles übertragen und die Verbindung von der Gegenstelle geschlossen worden. Sind die Meldungen größer, kann man die Daten zunächst in einen MemoryStream schieben.

    Guten Abend Elmar,

    kannst das nicht codemäßig wiedergeben. Kurzum ein konkretes Beispiel.

    Danke im Voraus.

    Viele Grüße Martin

    Dienstag, 22. November 2016 20:24
  • Hallo Martin,

    ich kann das in Code wiedergeben, ich dachte nur, dass es nicht notwendig wäre. Eine von mehreren Möglichkeiten, gekapselt in eigene Methoden, vollkommen testbefreit:

            internal async Task<string> ReadStringBlock(Stream netStream, int length)
            {
                var buffer = await ReadBlock(netStream, length);
                return Encoding.UTF8.GetString(buffer);
            }
    
            internal async Task<byte[]> ReadBlock(Stream netStream, int length)
            {
                byte[] buffer = new byte[length];
    
                int readOffset = 0;
                int readCount = length;
                while (readOffset < length)
                {
                    int resultLength = await netStream.ReadAsync(buffer, readOffset, readCount);
                    if (resultLength <= 0)
                        throw new InvalidDataException("ReadAsync failed");
                    readCount -= resultLength;
                    readOffset += resultLength;
                }
                return buffer;
            }
    

    Gruß Elmar

    Mittwoch, 23. November 2016 10:48
    Beantworter