0

Using VS2013, WPF 4.5, Caliburn Micro. I want to replace the old method for multithreading for tcp data listener on my working client app with the new "async await".

Since I am not familiar with them, I bought and read the book "Concurrency in C# cookbook" (Author: Mr. Stephen Cleary) and C# in Depth Third Edition (Author: Mr. Jon Skeet). Then I write a sample code (see below) first, because I don't want to mess up my client app that run on customer machines.

The problem: After I click the Connect-Button, the GUI is blocked/frozen. Click any button even the "x" button can't help. I need to kill the process using Task Manager.

What I have tried till now but still find no answer nor sample code that helps:

  • read the book of Mr. Cleary and also his blog for TCP Socket FAQ.
  • read the book of Mr. Skeet Part 5 Chapter 15.
  • contacted Mr. Cleary via E-Mail (get it from his blog), still no answer; maybe he is still busy.

Therefore I ask here. Please tell me how to solve the problem and feel free to modify my code. Thank you in advance

ShellViewModel

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Caliburn.Micro;

namespace TcpClientAsyncAwait.ViewModels
{
public class ShellViewModel : Conductor<Screen>
{
    private const Int64 MAX_DATA_LENGTH = 1024;
    private const String CRLF = "\r\n";

    private TcpClient _client;
    private List<String> _responseData;
    private Boolean _shouldStop;
    private NetworkStream _stream;
    private String _cmdConnect = "<StartConnect/>";

    public TcpClient Client
    {
        get { return _client; }
        set
        {
            _client = value;
            NotifyOfPropertyChange(() => Client);
        }
    }

    public List<String> ResponseData
    {
        get { return _responseData; }
        set
        {
            _responseData = value;
            NotifyOfPropertyChange(() => ResponseData);
        }
    }


    public ShellViewModel()
    {
    }

    public void Connect()
    {
        var hostIp = "127.0.0.1";
        var port = 16770;

        _client = new TcpClient(hostIp, port);
        var listenTask = StartListenerAsync();
        SendCmd(_cmdConnect);
    }

    private async Task StartListenerAsync()
    {
        _shouldStop = false;
        if (_client != null)
        {
            while (!_shouldStop)
            {
                await GetMessageAsync().ConfigureAwait(false);
            }
        }
    }

    private async Task GetMessageAsync()
    {
        try
        {
            if (_stream == null) _stream = _client.GetStream();
            if (_stream.DataAvailable)
            {
                Byte[] buffer = new Byte[MAX_DATA_LENGTH];
                Int32 readBytes = await _stream.ReadAsync(buffer, 0, buffer.Length);
                var receivedData = UTF8Encoding.UTF8.GetString(buffer, 0, readBytes);
                Trace.WriteLine(receivedData);
            }
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex.Message);
        }
    }

    private async Task SendCmd(string cmd)
    {
        if (_client != null)
        {
            // Manipulate command to fulfill format, that can be accepted by server
            Byte[] utf8Source = UTF8Encoding.UTF8.GetBytes(cmd);
            Byte[] suffix = UTF8Encoding.UTF8.GetBytes(CRLF);
            Byte[] utf8Result = new byte[utf8Source.Length + suffix.Length];
            Buffer.BlockCopy(utf8Source, 0, utf8Result, 0, utf8Source.Length);
            Buffer.BlockCopy(suffix, 0, utf8Result, utf8Source.Length, suffix.Length);

            if (_stream == null) _stream = _client.GetStream();
            using (var sw = new StreamWriter(_stream))
            {
                var data = UTF8Encoding.UTF8.GetString(utf8Result).ToCharArray();
                await sw.WriteAsync(data, 0, utf8Result.Length).ConfigureAwait(false);
                await sw.FlushAsync().ConfigureAwait(false);
            }
        }

    }

    public void Disconnect()
    {
        _shouldStop = true;
        if (_client != null) _client.Close();
    }
}
}

ShellView

<Window x:Class="TcpClientAsyncAwait.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        d:DesignHeight="300"
        d:DesignWidth="300"
        mc:Ignorable="d">
<Grid Width="300" Height="300">
<StackPanel Width="130" Height="120"
            HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button x:Name="Connect"
            Width="120" Height="50" Margin="5"
            HorizontalAlignment="Center" VerticalAlignment="Center"
            Content="Connect" />
    <Button x:Name="DisConnect"
            Width="120" Height="50" Margin="5"
            HorizontalAlignment="Center" VerticalAlignment="Center"
            Content="Disconnect" />
</StackPanel>
</Grid>
</Window>

EDIT:

Sample code is edited by adding namespace to ViewModel and XAML header to View due to completeness.

EDIT1: OK, I get a tcp server sample code from MSDN, that works as server for my client sample code. Here the steps to compile the server sample code.

  1. Download the C# version of that project from the link.
  2. Using VS2013: open the ClientApp.sln.
  3. In Solution Explorer: open project ServerApp and open Form1.cs to edit.
  4. Goto line 30 and edit the port 3000 to 16770. Save the change.
  5. Make the project ServerApp as startup project.
  6. Build and run it.

Now the steps how to create and compile my client sample code, so you can reproduce the problem I have. Please follow the steps exactly.

  1. Open VS2013
  2. Get "SideWaffle Template Pack" Extension from Visual Studio Gallery, install it and restart VS2013.
  3. Create "New Project" - choose "Caliburn.Micro WPF Application" project from SideWafle (see here) screenshot Set the solution name as TcpClientAsyncAwait
  4. Click "Tools - NuGet Package Manager - Package Manager Console". On the Window of Package Manage Console you will see a warning "Some NuGet packages are missing from this solution. Click to restore from your online package sources.". Just follow the instruction by clicking the Restore-Button
  5. After the download is completed, build the solution (usually I press "F6").
  6. In Solution Explorer: open "View" folder and open the ShellView.xaml. You will see ShellView.xaml.cs (the code behind). Just delete that file.
  7. Open ShellView.xaml and delete all code in it. Now you can copy & paste my sample code ShellView.xaml completely there.
  8. Open "ViewModels" folder and open ShellViewModel.cs to edit.
  9. Delete all code in it and copy & paste my sample code ShellViewModel.cs there.
  10. Build it.

Note: Please run the server before you run the client, because my sample code is so simple and has no reconnect and no "no-server" detection mechanism.

EDIT2:

According suggestion from TedyPranolo it seems the loop could be the problem. If I remove the loop the await GetMessageAsync().ConfigureAwait(false); is executed only once then ends. When the server sends another message later time, the client doesn't get the message anymore.

Q1: Now I want to know how to keep GetMessageAsync alive and always be ready to listen incoming message from server all the time till the application is closed. of course still in context "async await"

Q2: Or maybe "async await" does not fit for such purpose at all?

I am glad to get some solution suggestions from you all. Thank you in advance.

MagB
  • 2,131
  • 5
  • 28
  • 54
  • Can you provide a sample project I can test right away? – 123 456 789 0 Aug 13 '15 at 20:22
  • Looks like some serious blocking code in your Connect method a two items probably need Await, without code as ||| indicated its hard to debug your problem. – mvermef Aug 14 '15 at 01:10
  • The code above is my sample project. One just needs to add namespace to ShellViewModel and the common XAML header. I created the project as common WPF project and using Caliburn Micro MVVM Framework in References. I can only provide the client, since the server is a black box, belongs to company and must not be published. The server accepts and send only XML messages. Please tell me, what you need more specifically to be able to help. Thank you in advance. – MagB Aug 14 '15 at 09:56
  • Sorry, just got back from [ThatConference](https://www.thatconference.com/). I got so many emails while I was there, I had to shut it down for a while. I don't see anything deadlocky in the code, other than the `TcpClient` constructor will block (and can block for a long time). Can you post a [minimal, reproducible example](http://stackoverflow.com/help/mcve)? – Stephen Cleary Aug 14 '15 at 13:43
  • @Stephen Cleary can I send it as zip to you via email tomorrow (it is 11:30 pm here)? otherwise, suggest me other way. – MagB Aug 16 '15 at 21:23
  • @MagB: No. Please go through the exercise of reducing the example to a small enough amount of code that it can be posted here and reproduced by others. – Stephen Cleary Aug 17 '15 at 04:10
  • ok I will write a sample code for the server, that does create tcp listening socket, get message and reflect the incoming message back as receipt, so you can run my client sample code above with it and reproduce the problem. – MagB Aug 17 '15 at 07:47
  • @Stephen Cleary, lll and mvermef: I added the steps how to reproduce my client sample code and also link to the server sample code. I hope, you can reproduce the problem I have. Thanks. – MagB Aug 17 '15 at 09:16
  • I think you forgot to await this task. var listenTask = StartListenerAsync(); – Tedy Pranolo Aug 17 '15 at 09:25
  • @TedyPranolo: I added "await listenTask" and made the Connect()-method as async (because await can only be used in async method), built and run it. Unfortunately it is still frozen. But thank you anyway for your effort. – MagB Aug 17 '15 at 09:39
  • What about this line: while (!_shouldStop). It seems to me this is not what you intended do. What this will do is make multiple (infinite) calls to GetMessageAsync. It loops infinitely and prevent the UI thread from processing anything else. I think what you are trying to do is make it cancellable. In that case you would need to cancel the task instead of using a loop. Sorry I couldn't test this right now. – Tedy Pranolo Aug 17 '15 at 10:42
  • @TedyPranolo Maybe you are right. I used the loop while (_shouldStop) in my old style code that uses Thread(). That keep the GetMessage "alive" till the application is closed. Why? Because the listener should keep alive and be ready to get message. If I don't use the loop the listener can only get message once and then ends. Now I am trying to use the new async await. Maybe the loop doesn't fit and is not needed. – MagB Aug 18 '15 at 09:20

2 Answers2

1

Your UI thread might be stuck on the loop here. Since there's no delay within the loop and GetMessageAsync the thread is probably too busy to process anything else.

while (!_shouldStop)
{
      await GetMessageAsync().ConfigureAwait(false);
}

You may want to try the approach in this SO thread on how to do async socket. Here's the code in the answer by @spender, in case something happens with that thread

void Main()
{
    CancellationTokenSource cts = new CancellationTokenSource();
    TcpListener listener = new TcpListener(IPAddress.Any, 6666);
    try
    {
        listener.Start();
        //just fire and forget. We break from the "forgotten" async loops
        //in AcceptClientsAsync using a CancellationToken from `cts`
        AcceptClientsAsync(listener, cts.Token);
        Thread.Sleep(60000); //block here to hold open the server
    }
    finally
    {
        cts.Cancel();
        listener.Stop();
    }
}

async Task AcceptClientsAsync(TcpListener listener, CancellationToken ct)
{
    var clientCounter = 0;
    while (!ct.IsCancellationRequested)
    {
        TcpClient client = await listener.AcceptTcpClientAsync()
                                            .ConfigureAwait(false);
        clientCounter++;
        //once again, just fire and forget, and use the CancellationToken
        //to signal to the "forgotten" async invocation.
        EchoAsync(client, clientCounter, ct);
    }

}
async Task EchoAsync(TcpClient client,
                     int clientIndex,
                     CancellationToken ct)
{
    Console.WriteLine("New client ({0}) connected", clientIndex);
    using (client)
    {
        var buf = new byte[4096];
        var stream = client.GetStream();
        while (!ct.IsCancellationRequested)
        {
            //under some circumstances, it's not possible to detect
            //a client disconnecting if there's no data being sent
            //so it's a good idea to give them a timeout to ensure that 
            //we clean them up.
            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15));
            var amountReadTask = stream.ReadAsync(buf, 0, buf.Length, ct);
            var completedTask = await Task.WhenAny(timeoutTask, amountReadTask)
                                          .ConfigureAwait(false);
            if (completedTask == timeoutTask)
            {
                var msg = Encoding.ASCII.GetBytes("Client timed out");
                await stream.WriteAsync(msg, 0, msg.Length);
                break;
            }
            //now we know that the amountTask is complete so
            //we can ask for its Result without blocking
            var amountRead = amountReadTask.Result;
            if (amountRead == 0) break; //end of stream.
            await stream.WriteAsync(buf, 0, amountRead, ct)
                        .ConfigureAwait(false);
        }
    }
    Console.WriteLine("Client ({0}) disconnected", clientIndex);
}
Community
  • 1
  • 1
Tedy Pranolo
  • 1,285
  • 1
  • 14
  • 22
  • Well the code you have pasted is for server, not for client. My application is a client which should listen incoming message from server all the time till the application is closed. – MagB Aug 18 '15 at 09:28
1

Others already pointed that the code sample is missing some async and await here and there. Among all comments and body text of your question I got a little bit lost, but just to be sure I'll ask this:

have you checked that Connect() method (the one bound to button action) is marked as async void? That should be one of the few cases when async void is allowed.

EDIT after comment: make sure that method now looks similar to this:

public async void Connect()
{
    var hostIp = "127.0.0.1";
    var port = 16770;

    _client = new TcpClient(hostIp, port);

    await StartListenerAsync();  // no return value needed

    await SendCmdAsync(_cmdConnect);  // renamed for clarity & to adhere to conventions
}

Also, that while (!_shouldStop) sounds fishy. If you need to interrupt your async operation, you should create a TaskCancellationSource and pass that along the whole call chain where you need such interruption feature. E.g. if the following represents a call chain:

await YourMethodAsync(..., cancellationSource);
...
   await YourSubMethodAsync(..., cancellationSource);
   ...
      var result = await SomeDotNetMethodAsync(..., cancellationSource);

      await SomeOtherDotNetMethodAsync(..., cancellationSource);

If your custom methods just invoke out-of-the-box async methods, that should be enough. If your custom methods perform lenghty operations, or if they invoke long blocking-methods - e.g. a huge Buffer.BlockCopy() - then it is up to your method to break the long blocking operation into some kind of loop of shorter ones and check the cancellationSource at every loop.

EDIT after further re-reading your points: it seems that the root of everything is that you want your client to be aware of when the server sends something to it. If this is the case, you're looking for a bidirectional communication between the two.

One raw, low-level way to do that is long polling: the client from time to time asks the server if anything is ready. This is similar to what you're doing, but the client cannot continuously ed exclusively keep on polling the server. It should do that every X seconds/minutes/... and in the meantime do something else. So at the very minimum you should go with:

private async Task StartListenerAsync()
{
    _shouldStop = false;
    if (_client != null)
    {
        while (!_shouldStop)
        {
            await GetMessageAsync().ConfigureAwait(false);

            // non-blocking sleep equivalent
            await Task.Delay(pauseIntervalInMillis);
        }
    }
}

But there are much better ways than handling such a bidirectional communication from scratch. You could use a ready-made, tested library for this. Have a look at this SO thread, where e.g. they mention SignalR. If your server side was on node.js + socket.io, client side you could go for something like SocketIoClientDotNet.

Community
  • 1
  • 1
superjos
  • 12,189
  • 6
  • 89
  • 134
  • Yesterday I have tried a suggestion from user Tedy Pranolo (see comment #10 to my question) and replied him: "I added "await listenTask" and made the Connect()-method as async (because await can only be used in async method), built and run it. Unfortunately it is still frozen. But thank you anyway for your effort." – MagB Aug 18 '15 at 13:45