在多个线程之间拆分循环

时间:2017-02-24 09:20:08

标签: c# multithreading

我的代码从给定主机获取开放端口。 这个过程非常耗时

代码:

/

此过程对我来说需要 11s ! 如果我检查100或1000个端口,这是非常长的......

任何& 快速这样做的方法?

2 个答案:

答案 0 :(得分:5)

您不需要多个线程即可连接到多个端口。您可以使用ConnectAsync异步连接 。 Windows通过NT天的完成端口使用异步IO。

这意味着没有线程被阻止,操作系统在IO请求完成时通知原始线程。实际上,阻塞是模拟以使同步编程更容易。

您可以创建一个连接到单个端口的方法,并在完成后报告消息。尽管写入控制台,因此我将使用IProgress< T>界面报告进度:

public async Task ConnectToPortAsync(string host,int port,IProgress<string> progress)
{
    using(var client=new TcpClient())
    {
        try
        {
            await client.ConnectAsync(host,port).ConfigureAwait(false);
            progress.Report($"Port: {port} Open");
            //Do some more work
        }
        catch 
        {
            progress.Report($"Port {port} Closed");
        }
    }
}

假设您有一个端口列表:

var ports=new[]{80,8080,...};

var ports=Enumerable.Range(0,11);

您可以同时调用多个端口:

var host="127.0.0.1";
var progress=new Progress<string>(msg=>Console.WriteLine(msg););

var allTasks= ports.Select(port=>ConnectToPortAsync(host,port,progress));

await Task.WhenAll(allTasks);

此代码将使用主线程直到第一个await。这意味着将使用主线程创建所有TcpClient个实例。在那之后,代码将在必要时使用IO Completion线程池中的线程异步执行。

报告由主线程由Progress< T>对象执行,因此后台线程不会阻止等待写入控制台。

最后,一旦await返回,就会继续执行原始同步上下文。对于将成为主线程的桌面应用程序(WPF,Winforms)。如果要在异步操作后更新UI,则可以,但如果要在后台执行更多工作,则可能导致阻塞。通过使用ConfigureAwait(false),我们指示运行时继续处理后台线程

进一步改进:

您可以使用Task.Delay()Task.WhenAny添加超时,以防连接时间过长:

var connectionTask=client.ConnectAsync(host,port);
var timeout=Task.Delay(1000);
var completed=await Task.WhenAny(connectionTask,timeout);
if(completed==connectionTask)
{ 
    progress.Report($"Port {port} Open");
}
else
{
    progress.Report($"Port {port} Timeout");
}

答案 1 :(得分:3)

如果您要问的是如何并行运行多个查找,请查看Parallel类。

Parallel.For(0, 11, new ParallelOptions { MaxDegreeOfParallelism = 5 }, (port) =>
{
    string statusTCP = "Open";
    using (TcpClient tcp = new TcpClient())
    {
        try
        {
            tcp.Connect("127.0.0.1", port);
        }
        catch { statusTCP = "Close"; }
    }
    Console.WriteLine("Port " + port + " : " + statusTCP);
});

注意我在前几个方法参数中指定从0到10(因为11是独占的)并且与ParallelOptions类并行执行5次查找。