WebClient不支持并发I / O操作 - DownloadStringAsync - Scraping

时间:2017-09-26 17:22:22

标签: c# webclient blockingcollection

首先,我已经阅读了类似的问题,并没有给我一个连贯的解释。我使用BlockingCollection<WebClient> ClientQueue来提供Webclients。我给他们一个处理函数并启动异步抓取:

// Create queue of WebClient instances
BlockingCollection<WebClient> ClientQueue = new BlockingCollection<WebClient>();
for (int i = 0; i < 10; i++)
{
   ClientQueue.Add(new WebClient());
}

//Triggering Async Calls
foreach (var item in source)
{
   var worker = ClientQueue.Take();
   worker.DownloadStringCompleted += (sender, e) => HandleJson(sender, e, ClientQueue, item);
   worker.DownloadStringAsync(uri);
}

public static void HandleJson(object sender, EventArgs e, BlockingCollection<WebClient> ClientQueue, string item)
{
   var res = (DownloadStringCompletedEventArgs) e;
   var jsonData = res.Result;
   var worker = (WebClient) sender;
   var root = JsonConvert.DeserializeObject<RootObject>(jsonData);
   // Record the data
   while (worker.IsBusy) Thread.Sleep(5); // wait for the webClient to be free
   ClientQueue.Add(worker);
 }

我收到此错误消息:

  

WebClient不支持并发I / O操作。

其他主题:

  • 这里的答案建议问题是等到WebClient.IsBusy = false,但我在将webclient放回队列之前这样做了。我不明白为什么客户在自己做出IsBusy=false之后无法执行新请求 https://stackoverflow.com/a/9765812/7111121

  • 这里建议使用回收网络客户端来优化流程 https://stackoverflow.com/a/7474959/2132352

  • 这里建议实现一个新的WebClient(当然是简单的解决方案,但我不想隐藏所用对象的工作方式)。它还建议取消操作,但这没有帮助。

1 个答案:

答案 0 :(得分:1)

问题是,每次从队列中获取特定的WebClient时,都会为worker.DownloadStringCompleted事件注册新的事件处理程序而不取消注册以前的事件处理程序 - 因此事件处理程序会累积。因此,异步下载完成后会多次调用HandleJson,因此ClientQueue.Add(worker)也会将同一个客户端多次返回到队列。然后,在同一WebClient上发布两个并发下载只是时间问题。

通过在创建WebClient期间只注册一次事件处理程序,并从item方法中删除HandleJson参数,可以轻松修复此问题。

BlockingCollection<WebClient> ClientQueue = new BlockingCollection<WebClient>();
for (int i = 0; i < 2; i++)
{
    var worker = new WebClient();
    worker.DownloadStringCompleted += (sender, e) => HandleJson(sender, e, ClientQueue);
    ClientQueue.Add(worker);
}

如果需要参数item,请将其作为参数传递给DownloadStringAsync(uri, item)并从res.UserState读取:

foreach (var item in source)
{
   var worker = ClientQueue.Take();
   worker.DownloadStringAsync(uri, item);
}

public static void HandleJson(object sender, DownloadStringCompletedEventArgs e, BlockingCollection<WebClient> ClientQueue)
{
    string item = (string)res.UserState;
    ...
}