4

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

以下是我到目前为止所得到的:

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 中,不等待对 MessageLoop 的调用。我实际上不需要等待 MessageLoop 方法,因为该方法在连接断开之前不会返回。像这样使用异步是否被认为是一种好习惯?我听说不等待异步方法不好,但我也听说我应该消除不必要的等待。或者将异步模式用于消息循环甚至被认为是不好的?

4

2 回答 2

4

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

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实例,那么你就不必担心重新进入,但你仍然想观察异常。

于 2014-05-27T11:34:16.277 回答
3

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

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

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

于 2014-05-27T11:16:20.967 回答