在C#中使用异步方法进行消息循环

时间:2014-05-27 10:51:20

标签: c# asynchronous async-await message-loop

我正在制作一个在线通信应用程序,我想异步处理消息。我发现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中,不等待对MessageLoop的调用。 我实际上不需要等待MessageLoop方法,因为该方法在连接断开之前不会返回。 像这样使用异步被认为是一种好习惯吗?我听说不等待异步方法并不好,但我也听说我应该消除不必要的等待。或者甚至认为在消息循环中使用异步模式是不好的?

2 个答案:

答案 0 :(得分: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的"Async re-entrancy, and the patterns to deal with it"

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

答案 1 :(得分:3)

如果您不await MessageLoop(c, m,stream,cts.Token);,则只要遇到第一个await,该方法就会返回,然后执行方法的其余部分。这将是一场火灾和遗忘的场景。 UI线程不会引发异常,因此如果c.ClientDisconnected(m);抛出,它将在后台线程上引发并导致显式吞下的异常,因为存储在Task中的任何异常都是从没有观察到方法。您可以通过@Noseratio

this post中找到更多相关信息

老实说,似乎有点不同寻常。

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