Search code examples
c#asynchronousasync-awaitmessage-loop

Using async method for message loop in C#


I'm making an online communication application, and I'd like to process messages asynchronously. I found async-await pattern useful in implementing message loop.

Below is what I have got so far:

CancellationTokenSource cts=new CancellationTokenSource(); //This is used to disconnect the client.

public Action<Member> OnNewMember; //Callback field

async void NewMemberCallback(ConnectionController c, Member m, Stream stream){
    //This is called when a connection with a new member is established.
    //The class ConnectionController is used to absorb the difference in protocol between TCP and UDP.

    MessageLoop(c, m,stream,cts.Token);
    if(OnNewMember!=null)OnNewMember(m);
}

async Task MessageLoop(ConnectionController c, Member m, Stream stream, CancellationToken ct){
    MemoryStream msgbuffer=new MemoryStream();
    MemoryStream buffer2=new MemoryStream();

    while(true){
        try{
            await ReceiveandSplitMessage(stream, msgbuffer,buffer2,ct); //This stops until data is received.
            DecodeandProcessMessage(msgbuffer);
        catch( ...Exception ex){
            //When the client disconnects
            c.ClientDisconnected(m);
            return;
        }

    }
}

Then I got some warning saying that in NewMemberCallback, the call to MessageLoop is not awaited. I actually don't need to await the MessageLoop method because the method does not return until the connection is disconnected. Is it considered a good practice to use async like this? I heard that not awaiting an async method is not good, but I also heard that I should eliminate unnecessary await's. Or is it even considered bad to use async pattern for message loop?


Solution

  • Usually you want to keep track of the task(s) you've started, to avoid re-entrancy and handle exceptions. Example:

    Task _messageLoopTask = null;
    
    async void NewMemberCallback(ConnectionController c, Member m, Stream stream)
    {
        if (_messageLoopTask != null)
        {
            // handle re-entrancy
            MessageBox.Show("Already started!");
            return;
        }
    
        _messageLoopTask = MessageLoop(c, m,stream,cts.Token);
    
        if (OnNewMember!=null) OnNewMember(m);
    
        try
        {
            await _messageLoopTask;
        }
        catch (OperationCanceledException ex)
        {
            // swallow cancelation
        }
        catch (AggregateException ex) 
        { 
            // swallow cancelation
            ex.Handle(ex => ex is OperationCanceledException);
        }
        finally
        {
            _messageLoopTask = null;
        }
    }
    

    Check Lucian Wischik's "Async re-entrancy, and the patterns to deal with it".

    If you can have multiple MessageLoop instances, then you wouldn't have to worry about re-entrancy, but you'd still want to observe exceptions.