【问题标题】:Thread Queue Process线程队列进程
【发布时间】:2013-07-05 16:12:45
【问题描述】:

我正在使用 C# .Net4.0 在 Visual Studio 2010 中构建这个程序 目标是使用线程和队列来提高性能。

我有一个需要处理的网址列表。

string[] urls = { url1, url2, url3, etc.} //up to 50 urls

我有一个函数可以接收每个 url 并处理它们。

public void processUrl(string url) { 
    //some operation
}

最初,我创建了一个 for 循环来遍历每个 url。

for (i = 0; i < urls.length; i++)
    processUrl(urls[i]);

该方法有效,但程序很慢,因为它一个接一个地通过 url。

所以想法是使用线程来减少时间,但我不太确定如何处理。

假设我想创建 5 个线程同时处理。

当我启动程序时,它将开始处理前 5 个 url。完成后,程序开始处理第 6 个 url;当另一个完成时,程序开始处理第 7 个 url,依此类推。

问题是,我不知道如何实际创建 url 的“队列”并能够通过队列和处理。

谁能帮我解决这个问题?

-- 下午 1:42 编辑--

我在同时运行 5 个进程时遇到了另一个问题。

processUrl 函数涉及写入日志文件。如果多个进程同时超时,它们会同时写入同一个日志文件,我认为这会引发错误。

我假设这是问题所在,因为我收到的错误消息是“该进程无法访问文件 'data.log',因为它正被另一个进程使用。”

【问题讨论】:

  • Parallel.For 在您的情况下可能会更有用。请注意,拥有“更多”线程并不一定会使您的程序更快。
  • 将 processUrl 重写为异步方法,等待 IO 的异步方法并并行触发您的请求。不需要明确的线程代码。
  • async , await 仅适用于 .net framework 4.5
  • @spender 会好很多,但也需要升级到 VS 2012 :(
  • @srsyogesh 您可以在 .NET 4 中使用异步目标包进行操作,但仍然需要 VS 2012,而不是 2010...

标签: c# multithreading queue


【解决方案1】:

最简单的选择是只使用Parallel.ForEach。如果processUrl 是线程安全的,你可以这样写:

Parallel.ForEach(urls, processUrl);

我不建议限制为 5 个线程(调度程序会自动正常扩展),但这可以通过以下方式完成:

Parallel.ForEach(urls, new ParallelOptions { MaxDegreeOfParallelism = 5}, processUrl);

话虽如此,从本质上讲,URL 处理通常受 IO 限制,而不是 CPU 限制。如果您可以使用 Visual Studio 2012,更好的选择是重新设计它以使用该语言中新的 async 支持。这需要将您的方法更改为更像:

public async Task ProcessUrlAsync(string url)
{
    // Use await with async methods in the implementation...

然后您可以在循环中使用新的async 支持:

// Create an enumerable to Tasks - this will start all async operations..
var tasks = urls.Select(url => ProcessUrlAsync(url));

await Task.WhenAll(tasks); // "Await" until they all complete

【讨论】:

  • 可以进一步简化为Parallel.ForEach(urls, processUrl)。我也相信提问者使用的是 .NET 4.0,因此如果他们希望采用异步模式,则需要使用异步目标包
  • @Lukazoid 是的 - 我在那里提到过 - 只是改变了电话 - 好建议
  • @Reed - 我怎么知道我的 processUrl 是否是线程安全的?我对线程有点陌生,所以不太确定它是如何工作的。
  • @sora0419 您需要确保它不使用与其他类型或方法共享的任何“状态”。这是主要问题 - 如果它在不触及任何其他字段/属性/等的情况下工作,它可能没问题。 (不确定它实际上做了什么......)
  • @Reed processUrl 基本上接受一个 url 并返回该 url 的文本内容。是否可以跟踪实际成功通过的网址数量? (因为某些网址有时可能会超时)
【解决方案2】:

使用 Parallel Foreach 并将最大并行度设置为您想要的线程数(或将其留空并让 .NET 为您完成工作)

ParallelOptions parallelOptions = new ParallelOptions();

parallelOptions.MaxDegreeOfParallelism = 5;
Parallel.ForEach(urls, parallelOptions, url =>
{
   processUrl(url);
});

【讨论】:

  • 还有没有办法设置点火时间? processUrl 函数中有一个“写入日志”,如果多个进程超时,它们会同时将超时消息写入同一个日志文件,我认为这会引发错误。
  • 记录是另一回事。不,没有时间触发,因为线程将同时触发。如果您使用一些代码更新您尝试使用 w.r.t 日志记录的内容,我将相应地更新我的答案
【解决方案3】:

如果您真的想创建线程来完成您的任务而不是使用并行执行:

假设我希望每个 URL 有一个线程:

string[] urls = {"url1", "url2", "url3"};

我只是为每个 URL(或每个 5 个 URL)启动一个新的 Thread 实例:

foreach (var thread in urls.Select(url => new Thread(() => DownloadUrl(url))))
    thread.Start();

以及下载网址的方法:

private static void DownloadUrl(string url)
{
    Console.WriteLine(url);   
}

【讨论】:

  • 使用 TPL 甚至线程池可能比手动触发每个项目的线程更好...
  • 创建一个新线程可能是昂贵的操作,您可以使用 QueueUserWorkItem 类重新使用线程池中的线程。
  • 是的,我同意使用线程池,但是如果他使用固定数量的线程(在示例中只有 5 个),我们可以避免它。
  • @gustavodidomenico post specified "//up to 50 urls" - 此外,如果你不习惯线程,我认为 TPL 比 Thread 更易于使用;)
  • 我也同意你的看法。但请阅读我这篇文章的第一行。我说的是五个线程而不是五个 URL。
猜你喜欢
  • 2014-06-01
  • 2015-02-15
  • 1970-01-01
  • 2011-09-27
  • 1970-01-01
  • 2014-07-04
  • 2014-05-06
  • 1970-01-01
相关资源
最近更新 更多