2015-02-09 35 views
2

我正在写一个应用程序,我必须扫描一个IP(内联网)范围,并查看特定的IP是否对应于特定的URL。例如,假设我们有url:http://<ip>:8080/x/y,我们想看看能否找到一个在192.168.1.1 - 192.168.1.254范围内运行的活动服务器。显然,扫描过程不应该阻止用户界面。多个HttpClient异步请求到一个IP范围

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

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

,其目的是要开始的任务banch,试图获得一个有效的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()); 
        } 
       } 
      } 
     } 
    } 

如果请求是有效的,那么我们发现一个可用的服务器,因此我们将该网址添加到我们的名单。否则我们会发现一个例外。最后,我们更新我们的百分比变量,因为我们想将它们传递给我们的用户界面(xx%扫描)。每次任务完成时,它会触发由我们的用户界面使用的委托来获取可用服务器的新更新列表并完成百分比。 主要异步函数GetAvailableServers开始经由Task.Run(()=> className.GetAvailableServers()),其存在DelegateCommand内驻留到我自己的视图模型运行:

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

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

+2

在最大负载下将调试器暂停10次。每次查看UI线程堆栈。什么代码正在运行?这是滞后于你的用户界面的代码。 – usr 2015-02-09 22:51:12

+0

我尝试过,但我没有找到明确的东西给我。我还尝试删除与百分比更新相关的任何UI逻辑,并且仅使用Task.Run((=)=> utilsIp.GetAvailableServers())离开代码;我再次遇到滞后。看起来问题是当等待client.GetStringAsync抛出一个异常,(我有大量的异常导致在扫描过程中99%的IP根本不存在),但我不知道为什么我有这种行为,因为这应该在不同的线程上工作...... – jded 2015-02-15 16:03:22

+0

将URL更改为始终指向有效的端点。落后了吗?如果是的话,错误与这个问题有关。 – usr 2015-02-15 19:38:49

回答

0

如您所知(根据您的代码),当您在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"

我们可以从herehere看到的“GetHostEntry方法查询对与一个主机名或IP地址关联的IP地址的DNS服务器”,这意味着网络I/O,这意味着阻塞调用,这意味着laggy UI。

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