简体   繁体   English

多个HttpClient异步请求到一个IP范围

[英]Multiple HttpClient async requests to an ip range

I'm writing an app in which I have to scan an ip (intranet) range and see if the particular ip corresponds to a specific url. 我正在编写一个必须扫描ip(内部网)范围并查看特定ip是否对应于特定URL的应用程序。 For example let's say we have the url: http://<ip>:8080/x/y and we want to see if we can find an active server running in the range 192.168.1.1 - 192.168.1.254. 例如,假设我们具有url: http://<ip>:8080/x/y ,我们想看看是否可以找到运行在192.168.1.1-192.168.1.254范围内的活动服务器。 Obviously, the scan process should not block the UI. 显然,扫描过程不应阻止UI。

So I created the following async method: 因此,我创建了以下异步方法:

    private List<AvailableServer> _availableServerList;

    public List<AvailableServer> AvailableServerList
    {
        get { return _availableServerList;}
        set { _availableServerList = value;}
    }

    private Mutex objLock = new Mutex(true, "MutexForScanningServers");
    private long IPsChecked;
    private List<string> allPossibleIPs;

    CancellationTokenSource cts;

    public async Task GetAvailableServers()
    {
        Dictionary<string, string> localIPs = LocalIPAddress(); //new Dictionary<string, string>();

        allPossibleIPs = new List<string>();

        Dictionary<string, CancellationToken> kvrList = new Dictionary<string, CancellationToken>();
        cts = new CancellationTokenSource();

        foreach (KeyValuePair<string, string> kvp in localIPs)
        {
            allPossibleIPs.AddRange(getIpRange(kvp.Key.ToString(), kvp.Value.ToString()));
        }

        foreach (string ip in allPossibleIPs)
        {
            kvrList.Add(ip, cts.Token);
        }

        AvailableServerList = new List<AvailableServer>();

        var downloads = kvrList.Select(kvr => isServerAvailableAsync(kvr));
        Task[] dTasks = downloads.ToArray();
        await Task.WhenAll(dTasks).ConfigureAwait(false);
    }

whose purpose is to start a banch of Tasks, each one of them trying to receive a valid HttpClient request. 其目的是启动一组任务,每个任务都试图接收有效的HttpClient请求。 Let's say the request is valid if the HttpClient.GetStringAsync does not throw any exception: 假设如果HttpClient.GetStringAsync没有引发任何异常,则请求有效:

    private async Task isServerAvailableAsync(Object obj)
    {
        using (var client = new HttpClient())
        {
            try
            {
                KeyValuePair<string, CancellationToken> kvrObj = (KeyValuePair<string, CancellationToken>)obj;
                string urlToCheck = @"http://" + kvrObj.Key + ":8080/x/y";

                string downloadTask = await client.GetStringAsync(urlToCheck).ConfigureAwait(false);

                string serverHostName = Dns.GetHostEntry(kvrObj.Key).HostName;
                AvailableServerList.Add(new AvailableServer(serverHostName, @"http://" + serverHostName + ":8080/x/y"));
               // }
           }
            catch (System.Exception ex)
            {
                //...Do nothing for now...
            }
            finally
            {
                lock (objLock)
                {
                    //kvrObj.Value.ThrowIfCancellationRequested();
                    IPsChecked++;
                    int tmpPercentage = (int)((IPsChecked * 100) / allPossibleIPs.Count);
                    if (IPsCheckedPercentageCompleted < tmpPercentage)
                    {
                        IPsCheckedPercentageCompleted = tmpPercentage;
                        OnScanAvailableServersStatusChanged(new EventArgs());
                    }
                }
            }
        }
    }

If the request is valid then we found an available server and so we add the url to our list. 如果请求有效,则我们找到了可用的服务器,因此将URL添加到列表中。 Else we catch an exception. 否则我们会发现一个例外。 Finally we update our percentage variables, because we want to pass them to our UI (xx% scanned). 最后,我们更新百分比变量,因为我们希望将它们传递到我们的UI(已扫描xx%)。 Each time a Task completes it fires a delegate which is used by our UI to get the new updated list of available servers and percentage completed. 每次任务完成时,都会激发一个委托,UI会使用该委托来获取可用服务器的新更新列表和完成百分比。 The main async function GetAvailableServers starts running via Task.Run(() => className.GetAvailableServers()) which exists inside a DelegateCommand which resides into a ViewModel of my own: 主要的异步功能GetAvailableServers通过Task.Run(()=> className.GetAvailableServers())开始运行,该函数存在于DelegateCommand中,该命令位于我自己的ViewModel中:

    public ICommand GetAvailableServersFromViewModel
    {
        get
        {
            return new DelegateCommand
            {
                CanExecuteFunc = () => true,
                CommandAction = () =>
                {
                    Task.Run(() => utilsIp.GetAvailableServers());
                }
            };
        }
    }

The problem with my implementation is that the UI lags while scanning, which is easily seen through the loading spinner I have in my UI. 我的实现的问题是,UI在扫描时滞后,这在我UI中的加载微调器中很容易看到。 Well the code is far from best and I know I am wrong somewhere. 代码远非最佳,我知道我在某个地方错了。

As you know (based on your code), when you await a task on the UI thread, then the captured context for the task is the UI Context, which always runs on a single thread . 如您所知(基于您的代码),当您在UI线程上等待任务时,该任务捕获的上下文就是UI Context,该上下文始终在单个线程上运行

This means that when your task is complete, then control comes back in on the UI thread and then runs the continuation code on that thread. 这意味着当您的任务完成时,控件将返回到UI线程,然后在该线程上运行继续代码。

You can try to avoid this by using the .ConfigureAwait(false) method (which you are). 您可以尝试使用.ConfigureAwait(false)方法来避免这种情况。 That call turns off the configuring of the task's thread's synchronization context, so when the continuation code is ready to run it will use the default context, which means it will be executed on the thread pool instead of the UI thread where the task originated from. 该调用将关闭任务线程的同步上下文的配置,因此,在准备运行连续代码时,它将使用默认上下文,这意味着它将在线程池而不是任务起源的UI线程上执行。

However, as described here (and hinted at here ), if the task completes before it is awaited, then no thread switching is done and the continuation code will be executed on the UI thread. 然而,由于描述在这里 (和在暗示这里 ),如果之前等待任务完成,然后没有线程切换完成,继续码将在UI线程上执行。

This is where I think your problem is, since the code following the await code can sometimes run on the UI thread. 我认为这是您的问题所在,因为await代码之后的代码有时可以在UI线程上运行。 The code right after the await is Dns.GetHostEntry(...) and "GetHostEntry is ridiculously slow, particularly when no reverse DNS entry is found" . await之后的代码是Dns.GetHostEntry(...)“ GetHostEntry太慢了,特别是当找不到反向DNS条目时”

We can see from here and here that "the GetHostEntry method queries a DNS server for the IP address that is associated with a host name or IP address", which means network I/O, which means blocking call, which means laggy UI. 我们从这里这里可以看到,“ GetHostEntry方法向DNS服务器查询与主机名或IP地址关联的IP地址”,这意味着网络I / O,这意味着阻止调用,这意味着UI延迟。

That is a long way of saying that I believe that a solution to the issue is to also have GetHostEntry(...) wrapped in a task, just in case the first await doesn't block and it ends up being run on the UI thread. GetHostEntry(...)长说,我认为解决此问题的方法是也将GetHostEntry(...)包装在任务中,以防万一第一次await没有阻塞并且最终在UI上运行线。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM