C# 同时以异步方式等待服务器对不同调用的响应

C# 同时以异步方式等待服务器对不同调用的响应,c#,asynchronous,client,system.reactive,waithandle,C#,Asynchronous,Client,System.reactive,Waithandle,我正在为TCP上的模型铁路控制器编写一个客户端库。服务器嵌入在控制单元中 当客户端向服务器发送命令时,例如set(5,addr[3])服务器会用回复头响应该命令的结果以及是否存在错误 因为所有这些都是异步的,所以我必须用命令匹配回复。(即使客户端只发送一个命令,然后等待响应,也存在服务器端事件) 为了给这个库提供一个良好且易于理解的接口,我使用了asyncwait模式。这意味着客户机代码发出一个调用,如wait client.Set(5,“addr”,“3”)在服务器发回响应并由客户机代码评估响

我正在为TCP上的模型铁路控制器编写一个客户端库。服务器嵌入在控制单元中

当客户端向服务器发送命令时,例如
set(5,addr[3])
服务器会用回复头
响应该命令的结果以及是否存在错误

因为所有这些都是异步的,所以我必须用命令匹配回复。(即使客户端只发送一个命令,然后等待响应,也存在服务器端事件)

为了给这个库提供一个良好且易于理解的接口,我使用了
asyncwait
模式。这意味着客户机代码发出一个调用,如
wait client.Set(5,“addr”,“3”)
在服务器发回响应并由客户机代码评估响应后,代码继续

我目前正在通过一个
IDictionary
来实现这一点,其中字符串是命令,EventWaitHandle在方法
SendeBefehlAwaitResonse(string befehl)
wait Task.Run(()=>signal=e.WaitOne(timeout))中等待

有没有更常见的方法?对于NetworkClient,我还首先使用EventWaitHandle来等待发送新消息(并使用我的MessageDelay)属性。我发现使用无限循环调用
wait Task.Delay(100)具有更好的性能

问题:

  • 是否有更好的方法等待服务器响应?可能使用被动扩展或其他库
如果我必须重写图书馆的部分内容,这对我来说没什么大不了的。我写图书馆主要是为了学习。虽然代码(主要是TCP客户端)是某种黑客代码,但我尽力为项目提供更好、更易于理解的结构

提前感谢您的帮助


你可以在这里找到代码:|

Rx可能会让你的生活轻松很多。我还可以想象,它会减少代码中的一些潜在竞争条件,并且最终只会得到更少的管道样式代码(即维护缓存的代码)

如果我从我认为是代码的关键元素(从)开始,并将它们拉到方法中,我会看到这一点

private bool HasBeginAndEnd(string[] message)
{
    bool isValid = true;

    if (!message[0].StartsWith("<") || !message[0].EndsWith(">"))
        isValid = false;

    if (!message.Last().StartsWith("<END"))
        isValid = false;

    return isValid;
}
private bool IsReplyMessage(string[] message)
{
    return message.Length>0 && message[0].StartsWith("<REPLY ");
}
private BasicAntwort ParseResponse(string[] message)
{
    string header = message[0].Substring(7, message[0].Length - 8);
    return new BasicAntwort(message, header);
}
为了继续,我们还想添加一个超时,这样,如果在给定的时间(8000ms?)内我们没有从out命令获得响应,我们应该抛出。我们可以把它转换成一个任务,如果我们只需要一个值,那么我们也可以用Rx来完成

incomingMessages.Where(reply=>reply.Header == befehl)
                .Timeout(TimeSpan.FromSeconds(2))
                .Take(1)
                .ToTask();
现在差不多完成了。我们只需要一种发送命令并返回带有响应(或超时)的任务的方法。没问题。只需先订阅传入的消息序列,以避免竞争条件,然后发出命令

public Task<BasicAntwort> SendCommand(NetworkClient networkClient, string befehl)
{
    //Subscribe first to avoid race condition.
    var result = incomingMessages
                        .Where(reply=>reply.Header == befehl)
                        .Timeout(TimeSpan.FromSeconds(2))
                        .Take(1)
                        .ToTask();

    //Send command
    networkClient.SendMessage(befehl);

    return result;
}   
public Task SendCommand(NetworkClient-NetworkClient,字符串befehl)
{
//先订阅以避免竞争条件。
var结果=输入消息
.Where(reply=>reply.Header==befehl)
.超时(时间跨度从秒(2))
.采取(1)
.ToTask();
//发送命令
networkClient.SendMessage(befehl);
返回结果;
}   
以下是作为LinqPad脚本的完整代码

    void Main()
    {
        var _networkClient = new NetworkClient();
        var sendCommandTask = SendCommand(_networkClient, "MyCommand");
        BasicAntwort reply = sendCommandTask.Result;
        reply.Dump();
    }


    private static bool HasBeginAndEnd(string[] message)
    {
        bool isValid = true;

        if (!message[0].StartsWith("<") || !message[0].EndsWith(">"))
            isValid = false;

        if (!message.Last().StartsWith("<END"))
            isValid = false;

        return isValid;
    }
    private static bool IsReplyMessage(string[] message)
    {
        return message.Length>0 && message[0].StartsWith("<REPLY ");
    }
    private static BasicAntwort ParseResponse(string[] message)
    {
        string header = message[0].Substring(7, message[0].Length - 8);
        return new BasicAntwort(message, header);
    }

    public IObservable<BasicAntwort> Responses(NetworkClient networkClient)
    {
        return Observable.FromEventPattern<MessageReceivedEventArgs>(
                h => networkClient.MessageReceivedEvent += h,
                h => networkClient.MessageReceivedEvent -= h)
            .Select(x => x.EventArgs.Content)
            .Where(HasBeginAndEnd)
            .Where(IsReplyMessage)
            .Select(ParseResponse);
    }   

    public Task<BasicAntwort> SendCommand(NetworkClient networkClient, string befehl)
    {
        //Subscribe first to avoid race condition.
        var result = Responses(networkClient)
                            .Where(reply=>reply.Header == befehl)
                            .Timeout(TimeSpan.FromSeconds(2))
                            .Take(1)
                            .ToTask();

        //Send command
        networkClient.SendMessage(befehl);

        return result;
    }   


public class NetworkClient
{
public event EventHandler<MessageReceivedEventArgs> MessageReceivedEvent;
public bool Connected { get; set; }
public void SendMessage(string befehl)
{
    var handle = MessageReceivedEvent;
    if(handle!=null){

        var message = new string[3]{"<REPLY " + befehl +">", "Some content", "<END>"};

        handle(this, new UserQuery.MessageReceivedEventArgs(){Content=message});
    }
}
}
public class MessageReceivedEventArgs : EventArgs
{
public string[] Content { get; set; }
}

public class BasicAntwort
{
    public BasicAntwort(string[] message, string header)
    {
        Header = header;
        Message = message;
    }

    public string Header { get; set; }
    public string[] Message { get; set; }
}
void Main()
{
var_networkClient=新的networkClient();
var sendCommandTask=SendCommand(_networkClient,“MyCommand”);
BasicAntwort reply=sendCommandTask.Result;
reply.Dump();
}
私有静态bool hasbeginadend(字符串[]消息)
{
bool isValid=true;
如果(!消息[0].StartsWith(“”)
isValid=false;

if(!message.Last().StartsWith)(“如果快速连续发送相同的命令,会发生什么情况?是否也可能得不到响应?因为只有“绝对”命令,如“向左设置交叉点1”,而没有“切换交叉点1”等命令“我只发送此命令一次或重复使用服务器应答。是您的朋友。其他选项是按顺序匹配响应。我没有编写该协议,它是由提供的。我建议您使用Rx或内部连续
async
读取(以表示传入的响应流/服务器更新)。然后为“发送此命令并等待其匹配响应”添加一个单独的更高级别的
async
API。非常感谢!我将按照您编写的方式实现此命令,并在接下来的几天中删除一些其他内容。因为我从未执行过Rx,这可能需要一些时间。我将随时通知您。再次:非常感谢!
public Task<BasicAntwort> SendCommand(NetworkClient networkClient, string befehl)
{
    //Subscribe first to avoid race condition.
    var result = incomingMessages
                        .Where(reply=>reply.Header == befehl)
                        .Timeout(TimeSpan.FromSeconds(2))
                        .Take(1)
                        .ToTask();

    //Send command
    networkClient.SendMessage(befehl);

    return result;
}   
    void Main()
    {
        var _networkClient = new NetworkClient();
        var sendCommandTask = SendCommand(_networkClient, "MyCommand");
        BasicAntwort reply = sendCommandTask.Result;
        reply.Dump();
    }


    private static bool HasBeginAndEnd(string[] message)
    {
        bool isValid = true;

        if (!message[0].StartsWith("<") || !message[0].EndsWith(">"))
            isValid = false;

        if (!message.Last().StartsWith("<END"))
            isValid = false;

        return isValid;
    }
    private static bool IsReplyMessage(string[] message)
    {
        return message.Length>0 && message[0].StartsWith("<REPLY ");
    }
    private static BasicAntwort ParseResponse(string[] message)
    {
        string header = message[0].Substring(7, message[0].Length - 8);
        return new BasicAntwort(message, header);
    }

    public IObservable<BasicAntwort> Responses(NetworkClient networkClient)
    {
        return Observable.FromEventPattern<MessageReceivedEventArgs>(
                h => networkClient.MessageReceivedEvent += h,
                h => networkClient.MessageReceivedEvent -= h)
            .Select(x => x.EventArgs.Content)
            .Where(HasBeginAndEnd)
            .Where(IsReplyMessage)
            .Select(ParseResponse);
    }   

    public Task<BasicAntwort> SendCommand(NetworkClient networkClient, string befehl)
    {
        //Subscribe first to avoid race condition.
        var result = Responses(networkClient)
                            .Where(reply=>reply.Header == befehl)
                            .Timeout(TimeSpan.FromSeconds(2))
                            .Take(1)
                            .ToTask();

        //Send command
        networkClient.SendMessage(befehl);

        return result;
    }   


public class NetworkClient
{
public event EventHandler<MessageReceivedEventArgs> MessageReceivedEvent;
public bool Connected { get; set; }
public void SendMessage(string befehl)
{
    var handle = MessageReceivedEvent;
    if(handle!=null){

        var message = new string[3]{"<REPLY " + befehl +">", "Some content", "<END>"};

        handle(this, new UserQuery.MessageReceivedEventArgs(){Content=message});
    }
}
}
public class MessageReceivedEventArgs : EventArgs
{
public string[] Content { get; set; }
}

public class BasicAntwort
{
    public BasicAntwort(string[] message, string header)
    {
        Header = header;
        Message = message;
    }

    public string Header { get; set; }
    public string[] Message { get; set; }
}