0

I have a Forms app and a Console server app connected with TCPClient. The Forms app opens up a dialog for user authentication, when user clicks the Login button, a request is sent to the server. The server then responds if the login credentials were valid or not. The Forms app receives the response asynchronously and gives out an error message or closes the authentication window and continues.

After reading this post about SynchronizationContext I have created a working solution which calls functions on the UI thread from the thread that handles the server response. My question is, is there a better way to do this in C#?

I read quite a bit on async/await but haven't found a way to await for a particular server response. The Client handles the responses using stream.BeginRead() from a buffer.

Capturing UI Context:

public static SynchronizationContext uiContext;

public MainForm(string[] args)
{
    InitializeComponent();
}

private void MainForm_Load(object sender, EventArgs e)
{
    uiContext = SynchronizationContext.Current;
    //etc
}

Calling functions on the LoginForm when Client receives particular server response for authentication through stream.BeginRead():

private void UserAuthenticationAnswerReceived(byte[] _data)
{
    SynchronizationContext uiContext = MainForm.uiContext;
    //if auth was unsuccessful, the data will be only 1 int in length
    if (_data.Length <= sizeof(int))
    {
        Debug.WriteLine("Authentication failed.");
        uiContext.Post(UserLogin.userLoginFrm.AuthUnsuccessfulAsync, null);
    }
    else
    {
        Debug.WriteLine("Authentication successful.");
        uiContext.Post(UserLogin.userLoginFrm.AuthSuccessfulAsync, user);
    }
}

I am quite new to networking and may be missing a more fundamental concept about client/server model and how to manage responses asynchronously.

Tsaras
  • 455
  • 2
  • 4
  • 18
  • It depends a bit on who calls `UserAuthenticationAnswerReceived()` but I have solved similar tasks in the past by having a read-loop in it's own task and using the [`IProgress`](https://learn.microsoft.com/en-us/dotnet/api/system.iprogress-1?view=net-6.0) Interface to post received data to other threads such as the UI. – Roland Deschain Dec 27 '21 at 11:04
  • When ```stream.BeginRead()```'s callback gets called, it forwards the received data to a routine which depending on an identifier calls the respective routine, here ```UserAuthenticationAnswerReceived()```. – Tsaras Dec 27 '21 at 11:53
  • OK, never used `BeginRead()`, I normally use `stream.ReadAsync()` – Roland Deschain Dec 27 '21 at 11:59

1 Answers1

1

My question is, is there a better way to do this in C#?

Generally, it's easier to use Task-based APIs such as ReadAsync instead of old-style IAsyncResult-based APIs such as BeginRead.

But for the general solution of notifying the UI from a background thread, the best solution is to capture the SynchronizationContext.

However, if you have a command/response protocol, with the client only receiving responses for commands it has sent, then you can get a much simpler solution using async/await.

I am quite new to networking and may be missing a more fundamental concept about client/server model and how to manage responses asynchronously.

The first thing I'd recommend is to self-host an ASP.NET Core WebApi. Then you can use an off-the-shelf HttpClient to handle the commands and responses. Custom network protocols are extremely complex to get right, and they're even more complex if you're not familiar with asynchronous code.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • How could someone use ```async```/```await``` to wait for a response? In a more general case, how could it be used to halt execution until a particular function which is not known beforehand gets executed? The only thing I can think of is using another global object with flags that the function can wait to trigger and resume execution from ```await```. – Tsaras Dec 31 '21 at 11:30
  • 1
    @Tsaras: If you're using an off-the-shelf HTTP API, then you would use `await httpClient.PostAsync(...)`. If you're designing your own custom protocol, then you need to have your own `TaskCompletionSource` created when the command is sent and completed when the response is received. – Stephen Cleary Dec 31 '21 at 14:28