c# - 对 ip 范围的多个 HttpClient 异步请求

标签 c# concurrency async-await .net-4.5 dotnet-httpclient

我正在编写一个应用程序,我必须在其中扫描一个 ip(内联网)范围并查看特定 ip 是否对应于特定 url。例如,假设我们有 url:http://<ip>:8080/x/y我们想看看是否可以找到在 192.168.1.1 - 192.168.1.254 范围内运行的事件服务器。显然,扫描进程不应阻塞 UI。

所以我创建了以下异步方法:

    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);
    }

其目的是启动一组任务,每个任务都尝试接收有效的 HttpClient 请求。假设如果 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());
                    }
                }
            }
        }
    }

如果请求有效,那么我们找到了一个可用的服务器,因此我们将 url 添加到我们的列表中。否则我们会捕获异常。最后我们更新我们的百分比变量,因为我们想将它们传递给我们的 UI(xx% 扫描)。每次任务完成时,它都会触发一个委托(delegate),我们的 UI 使用该委托(delegate)来获取可用服务器的新更新列表和完成百分比。 主要的异步函数 GetAvailableServers 通过 Task.Run(() => className.GetAvailableServers()) 开始运行,它存在于一个 DelegateCommand 中,它驻留在我自己的 ViewModel 中:

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

我的实现的问题是 UI 在扫描时滞后,这很容易通过我的 UI 中的加载微调器看出。好吧,代码远非最佳,我知道我在某个地方错了。

最佳答案

如您所知(根据您的代码),当您在 UI 线程上等待任务时,为该任务捕获的上下文就是 UI 上下文,即 always runs on a single thread .

这意味着当您的任务完成时,控制权会返回到 UI 线程,然后在该线程上运行延续代码。

您可以尝试通过使用.ConfigureAwait(false) 方法(您就是)来避免这种情况。该调用关闭了任务线程同步上下文的配置,因此当延续代码准备好运行时,它将使用默认上下文,这意味着它将在线程池而不是任务发起的 UI 线程上执行。

但是,正如描述here (并在 here 中提示),如果任务在等待之前完成,则不会进行线程切换,继续代码将在 UI 线程上执行。

这就是我认为您的问题所在,因为 await 代码之后的代码有时可以在 UI 线程上运行。 await 之后的代码是 Dns.GetHostEntry(...)"GetHostEntry is ridiculously slow, particularly when no reverse DNS entry is found" .

here可以看出和 here “GetHostEntry 方法向 DNS 服务器查询与主机名或 IP 地址关联的 IP 地址”,这意味着网络 I/O,这意味着阻塞调用,这意味着滞后的 UI。

我相信这个问题的解决方案是将 GetHostEntry(...) 包装在任务中,以防万一第一个 await 不会阻塞,它最终会在 UI 线程上运行。

关于c# - 对 ip 范围的多个 HttpClient 异步请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28420894/

相关文章:

c# - 如何在 Windows Azure Web Apps 中发布和使用字体?

c# - 为 WebGrid MVC 3 上的单列指定格式

scala - future /成功竞赛

c++ - 使用shared_ptr实现RCU(读取-复制-更新)?

javascript - 异步/等待, "unresolved variable or type await"

c# - 使用简单注入(inject)器注册多个DbContext

c# - 使用 TTS 将 MemoryStream 的音频输出到 Discord Bot

java - ConcurrentHashMap 重新排序指令?

node.js - 异步回调内的 Mocha 测试

javascript - mqtt 异步等待消息然后响应 http post 请求