如何将Task.WhenAny与ReadLineAsync结合使用以从任何TcpClient获取数据 [英] How to use Task.WhenAny with ReadLineAsync to get data from any TcpClient

查看:68
本文介绍了如何将Task.WhenAny与ReadLineAsync结合使用以从任何TcpClient获取数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在所有异步/等待(来自线程池)中工作,我遇到了一个有趣的挑战.

working my way through all that is async / await (coming from threadpools) and I've hit an interesting challenge.

我有一个在WPF应用程序中运行的TCP服务器,该服务器接受客户端并将其存储在List<>中,例如:

I have a TCP Server running in a WPF application that accepts clients and stores them in a List<> like such:

private List<Client> clients = new List<Client>();
while (running && clientCount <= maxClients)
{
    Client client = new Client(await server.AcceptTcpClientAsync());
    await client.WriteLineAsync("Connected to the Server!");
    clients.Add(client);
    clientCount++;
}

所以我要做的是遍历我的客户列表,如果接收到任何数据,我想将其附加到文本框中.我意识到这可能不是实现此目标的最佳方法,我愿意提出建议,但这就是我目前的结构方式.

So what I want to do is iterate through the list of my Clients and if any data is received, I want to append it to a textbox. I realize this may not be the best way to achieve this, and I'm open to suggestions, but this is how I currently have it structured.

一个按钮开始循环并不断调用并等待AllReadLineAsync()

A button starts the loop and continuously calls and awaits AllReadLineAsync()

private async void btnStartReadLoopClick(object sender, RoutedEventArgs e)
{
    btnStartReadLoop.IsEnabled = false;
    while(server.clientCount > 0)
    {
        string text = await server.AllReadLineAsync();
        txtOutputLog.AppendText("[client] " + text + "\n");
    }
}

此功能:

public async Task<string> AllReadLineAsync()
{
    var tasklist = new List<Task<string>>();

    foreach (var client in clients)
        tasklist.Add(client.ReadLineAsync());

    while (tasklist.Count > 0)
    {
        Task<string> finishedTask = await Task.WhenAny(tasklist);
        if (finishedTask.Status == TaskStatus.RanToCompletion)
            return await finishedTask;

        tasklist.Remove(finishedTask);
    }

    return "Error: No task finished";
}

此功能遍历客户端列表,并创建所有ReadLineAsync()任务的List<Tast<string>>.

This function iterates through the list of clients and creates a List<Tast<string>> of all the ReadLineAsync() tasks.

在任何给定时间,我实际上可能只有1个或2个客户端正在发送数据,所以我不能WhenAll(),并且我尝试WhenAny()WaitAny()都没有成功.

At any given time, I may only have 1 or 2 clients actually sending data, so I can't WhenAll() and I've tried WhenAny() and WaitAny() without success.

未来的Google员工注意事项:WaitAny()Wait()类似,并且已被屏蔽.不要在UI线程上执行此操作.而是使用WhenAny()并等待它.

Note to future googlers: WaitAny() is like Wait() and is blocking. Do not do this on a UI thread. Instead use WhenAny() and await it.

所以我当前的实现方式是可行的,但是我无法弄清楚该错误,如果其他客户端不发送数据,则消息将从一个客户端建立.

So my current implementation kind of works, but I can't figure out this bug where messages will build up from 1 client if the other clients don't send data.

TL; DR::我是在正确使用WhenAny()还是有更好的方法来等待ReadLineAsync并将结果传递到文本框?

TL;DR: Am I using WhenAny() correctly or is there a better way for me to await ReadLineAsync and pass the result to a textbox?

编辑:这是我看到的行为 我按以下顺序输入:左,右,左2,右2,左3,右3 好像有一些邮件被丢弃了?

Here's the behavior I'm seeing I typed in this order: Left, Right, Left 2, Right 2, Left 3, Right 3 and it appears as if some messages are being dropped?

,我找到了我在MSDN博客上复制的代码段的来源: https://blogs.msdn.microsoft.com/pfxteam/2012/08/02/processing-tasks-as-they-complete/

EDIT 2: I found the source of the code snippet I copied on the MSDN blog: https://blogs.msdn.microsoft.com/pfxteam/2012/08/02/processing-tasks-as-they-complete/

此代码段专门用于遍历一系列任务,以确保所有任务均已完成.我不在乎任务是否重复,因此我需要修改代码以始终检查整个任务列表,而不是删除任何任务.

This code snippet is meant specifically to iterate through a list of tasks ensuring they all are completed. I don't care if tasks are duplicated though so I need to modify the code to always check the entire tasklist instead of removing any tasks.

推荐答案

好的,所以我弄清楚了哪里出了问题以及为什么以前的代码无法正常工作.

Alright so I figured out where I was going wrong and why my previous code didn't work.

首先,让我们讨论一下这段代码的作用以及为什么它不起作用:

First off, let's talk about what this code does, and why it doesn't work:

public async Task<string> AllReadLineAsync()
{
    var tasklist = new List<Task<string>>();

    foreach (var client in clients)
        tasklist.Add(client.ReadLineAsync());

    while (tasklist.Count > 0)
    {
        Task<string> finishedTask = await Task.WhenAny(tasklist);
        if (finishedTask.Status == TaskStatus.RanToCompletion)
            return await finishedTask;

        tasklist.Remove(finishedTask);
    }

    return "Error: No task finished";
}

1)创建await ReadLineAsync()

2)当该列表的大小大于0时,我们等待所有ReadLineAsync函数.

2) While the size of that list is greater than 0, we wait for await any of the ReadLineAsync functions.

3)当我们点击完成的任务时,我们返回它的字符串,并退出该函数

3) When we hit a Task that has finished, we return it's string, and exit the function

4)所有其余尚未完成的ReadLineAsync函数仍在运行,但是我们丢失了对其实例的引用.

4) Any remaining ReadLineAsync functions that did not finish are still running, but we lost the reference to their instance.

5)此函数循环,完成后立即调用AllReadAsync().

5) This function loops, calling AllReadAsync() immediately after it finishes.

6)这使我们尝试从第4步开始等待StreamReady的同时访问它-因此抛出了异常.

6) This causes us to try and access the StreamReady while it is still being awaited from step 4 - Thus throwing an excpetion.

由于此结构,我无法提出在应用程序中使用WhenAny()的方法.相反,我将此函数添加到了我的客户端类中:

Because of the structure of this, I could not come up with a way to use WhenAny() in my application. Instead I added this function to my Client Class:

public async Task<string> CheckForDataAsync()
{
    if (!stream.DataAvailable)
    {
        await Task.Delay(5);
        return "";
    }

    return await reader.ReadLineAsync();
}

我们而不是等待ReadLineAsync(),而是从TcpClient访问NetworkStream,然后检查是否有可用数据if(!stream.DataAvailable),如果没有,我们将返回一个空字符串,否则返回await ReadLineAsync()因为我们知道我们有传入的数据,并且希望能收到整条线.

Instead of awaiting ReadLineAsync(), we instead access the NetworkStream from the TcpClient, and we check if there is data available, if(!stream.DataAvailable), if there is not, we return early with an empty string, else we await ReadLineAsync() because we know we have incoming data, and we expect to receive the whole line.

然后,我们用以下内容替换我所谈论的第一个功能AllReadLineAsync():

We then replace the first function I talked about, AllReadLineAsync() With the following:

public async Task<string> AllReadLineAsync()
{
    string data = "", packet = "";
    foreach (var client in clients)
    {
        data = await client.CheckForDataAsync();

        if (data != string.Empty)
            packet += string.Format($"[client] {data}\n");   
    }

    return packet;
}

哪个比我以前尝试的方法还要简单.现在,这会在for循环中遍历所有客户端,并在每个客户端上调用CheckForDataAsync()函数.由于此函数将尽早返回,而不是无限等待完整的ReadLineAsync(),因此在AllReadLineAysnc()函数结束后,它将不会继续在后台运行.

Which is even simpler than the previous way I was trying. This now iterates through all of our clients in a for loop, and calls the CheckForDataAsync() function on each client. Since this functions returns early instead of infinitely awaiting a full ReadLineAsync() it does not continue to run in the background after the AllReadLineAysnc() function ends.

在完成所有客户端的循环之后,我们将字符串包返回到UI上下文,然后可以在其中将数据添加到文本框中,如下所示:

After we finish looping through all of our clients, we take our string packet and return it to the UI context, where we can then add our data to a text box as such:

private async void RecvData(object sender, RoutedEventArgs e)
{
    while(server.hasClient)
    {
        string text = await server.AllReadLineAsync();
        txtOutputLog.AppendText(text);
    }
}

就是这样.这就是我在WPF应用程序中处理多个TcpClient的方式.

And that's it. That's how I'm handling multiple TcpClients from within a WPF application.

这篇关于如何将Task.WhenAny与ReadLineAsync结合使用以从任何TcpClient获取数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆