在c#中使用async方法实现消息循环

本文关键字:方法 实现 消息 循环 async | 更新日期: 2023-09-27 18:05:25

我正在制作一个在线通信应用程序,我想异步处理消息。我发现async-await模式在实现消息循环时很有用。

下面是我到目前为止得到的:

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;
        }
    }
}

然后我得到一些警告说,在NewMemberCallback中,对messagelloop的调用没有等待。我实际上不需要等待MessageLoop方法,因为该方法在连接断开之前不会返回。像这样使用异步被认为是一个好的实践吗?我听说不等待异步方法是不好的,但我也听说我应该消除不必要的等待。或者在消息循环中使用异步模式被认为是不好的?

在c#中使用async方法实现消息循环

通常您希望跟踪已启动的任务,以避免重入并处理异常。例子:

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;
    }
}

查看Lucian Wischik的"异步重入,以及处理它的模式"。

如果您可以有多个MessageLoop实例,那么您不必担心可重入性,但您仍然希望观察异常。

如果没有await MessageLoop(c, m,stream,cts.Token);,方法将在遇到第一个await时返回,然后执行方法的其余部分。这将是一场欲罢不能的场景。异常不会在UI线程上引发,所以如果c.ClientDisconnected(m);抛出,它将在后台线程上引发并导致显式吞下的异常,因为从该方法返回的Task中存储的任何异常都不会被观察到。你可以在@Noseratio的这篇文章中找到更多信息

老实说,这似乎有点不合常规。

是否有更好的方法来确定客户端是否已断开连接?您将错过您可能希望向用户或日志显示的任何重要异常。