C# 在C中线程数量未知#

C# 在C中线程数量未知#,c#,asp.net,multithreading,C#,Asp.net,Multithreading,我目前正在编写一个站点地图生成器,它可以从站点中获取URL并构建一个xml站点地图。由于大部分等待时间都花在对uri的请求上,所以我使用线程,特别是内置线程池对象 为了让主线程等待未知数量的线程完成,我实现了以下设置。我觉得这不是一个好的解决方案,但是,任何线程专家都能告诉我这个解决方案有什么问题,或者建议一个更好的方法来实现它吗 EventWaitHandle设置为EventResetMode.ManualReset 下面是线程方法 protected void CrawlUri(object

我目前正在编写一个站点地图生成器,它可以从站点中获取URL并构建一个xml站点地图。由于大部分等待时间都花在对uri的请求上,所以我使用线程,特别是内置线程池对象

为了让主线程等待未知数量的线程完成,我实现了以下设置。我觉得这不是一个好的解决方案,但是,任何线程专家都能告诉我这个解决方案有什么问题,或者建议一个更好的方法来实现它吗

EventWaitHandle设置为EventResetMode.ManualReset

下面是线程方法

protected void CrawlUri(object o)
    {

        try
        {
            Interlocked.Increment(ref _threadCount);
            Uri uri = (Uri)o;

            foreach (Match match in _regex.Matches(GetWebResponse(uri)))
            {
                Uri newUri = new Uri(uri, match.Value);

                if (!_uriCollection.Contains(newUri))
                {
                    _uriCollection.Add(newUri);
                    ThreadPool.QueueUserWorkItem(_waitCallback, newUri);
                }
            }
        }
        catch
        {
            // Handle exceptions
        }
        finally
        {
            Interlocked.Decrement(ref _threadCount);
        }

        // If there are no more threads running then signal the waithandle
        if (_threadCount == 0)
            _eventWaitHandle.Set();
    }
// Request first page (based on  host)
Uri root = new Uri(context.Request.Url.GetLeftPart(UriPartial.Authority));

// Begin threaded crawling of the Uri
ThreadPool.QueueUserWorkItem(_waitCallback, root);
Thread.Sleep(5000); // TEMP SOLUTION: Sleep for 5 seconds
_eventWaitHandle.WaitOne();

 // Server the Xml Sitemap
 context.Response.ContentType = "text/xml";
 context.Response.Write(GetXml().OuterXml);
这里是主线程方法

protected void CrawlUri(object o)
    {

        try
        {
            Interlocked.Increment(ref _threadCount);
            Uri uri = (Uri)o;

            foreach (Match match in _regex.Matches(GetWebResponse(uri)))
            {
                Uri newUri = new Uri(uri, match.Value);

                if (!_uriCollection.Contains(newUri))
                {
                    _uriCollection.Add(newUri);
                    ThreadPool.QueueUserWorkItem(_waitCallback, newUri);
                }
            }
        }
        catch
        {
            // Handle exceptions
        }
        finally
        {
            Interlocked.Decrement(ref _threadCount);
        }

        // If there are no more threads running then signal the waithandle
        if (_threadCount == 0)
            _eventWaitHandle.Set();
    }
// Request first page (based on  host)
Uri root = new Uri(context.Request.Url.GetLeftPart(UriPartial.Authority));

// Begin threaded crawling of the Uri
ThreadPool.QueueUserWorkItem(_waitCallback, root);
Thread.Sleep(5000); // TEMP SOLUTION: Sleep for 5 seconds
_eventWaitHandle.WaitOne();

 // Server the Xml Sitemap
 context.Response.ContentType = "text/xml";
 context.Response.Write(GetXml().OuterXml);

任何想法都很受欢迎:)

在执行这种逻辑时,我通常会尝试创建一个对象来表示每个异步任务及其运行所需的数据。我通常会将此对象添加到要完成的任务集合中。线程池会重新安排这些任务,我会在任务完成时让对象本身从“待完成”集合中移除,可能会在集合本身上发出信号


因此,当“待完成”集合为空时,您就完成了;主线程可能会被完成的每个任务唤醒一次

首先,您可以创建一个手动重置事件,该事件开始时未设置,这样您就不必在等待它之前睡觉。其次,您需要在Uri集合周围进行线程同步。您可能会得到一个竞争条件,其中一个线程和两个线程通过了“ThisURI还不存在”检查,并添加了重复项。另一个争用条件是两个线程可以通过
if(\u threadCount==0)
检查,并且它们都可以设置事件

最后,通过使用异步BeginGetRequest,可以使整个过程更加高效。您的解决方案现在有一个线程等待每个请求。如果您使用异步方法和回调,您的程序将使用更少的内存(每个线程1MB),并且几乎不需要对线程进行上下文切换

这里有一个例子可以说明我所说的。出于好奇,我对它进行了测试(有深度限制),它确实有效

public class CrawlUriTool
{
    private Regex regex;
    private int pendingRequests;
    private List<Uri> uriCollection;
    private object uriCollectionSync = new object();
    private ManualResetEvent crawlCompletedEvent;

    public List<Uri> CrawlUri(Uri uri)
    {
        this.pendingRequests = 0;
        this.uriCollection = new List<Uri>();
        this.crawlCompletedEvent = new ManualResetEvent(false);
        this.StartUriCrawl(uri);
        this.crawlCompletedEvent.WaitOne();

        return this.uriCollection;
    }

    private void StartUriCrawl(Uri uri)
    {
        Interlocked.Increment(ref this.pendingRequests);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);

        request.BeginGetResponse(this.UriCrawlCallback, request);
    }

    private void UriCrawlCallback(IAsyncResult asyncResult)
    {
        HttpWebRequest request = asyncResult.AsyncState as HttpWebRequest;

        try
        {
            HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);

            string responseText = this.GetTextFromResponse(response); // not included

            foreach (Match match in this.regex.Matches(responseText))
            {
                Uri newUri = new Uri(response.ResponseUri, match.Value);

                lock (this.uriCollectionSync)
                {
                    if (!this.uriCollection.Contains(newUri))
                    {
                        this.uriCollection.Add(newUri);
                        this.StartUriCrawl(newUri);
                    }
                }
            }
        }
        catch (WebException exception)
        {
            // handle exception
        }
        finally
        {
            if (Interlocked.Decrement(ref this.pendingRequests) == 0)
            {
                this.crawlCompletedEvent.Set();
            }
        }
    }
}
公共类爬虫工具
{
私人正则表达式正则表达式;
私人待决请求;
私人收藏清单;
私有对象uriCollectionSync=新对象();
private ManualResetEvent爬行完成事件;
公共列表爬网Uri(Uri)
{
this.pendingRequests=0;
this.uriCollection=新列表();
this.crawlpletedevent=新的手动重置事件(false);
这个.StartUriCrawl(uri);
这个.crawlpletedevent.WaitOne();
返回此.URI集合;
}
私有void StartUriCrawl(Uri)
{
互锁增量(参考本pendingRequests);
HttpWebRequest请求=(HttpWebRequest)WebRequest.Create(uri);
request.BeginGetResponse(this.UriccrawlCallback,request);
}
私有回调(IAsyncResult asyncResult)
{
HttpWebRequest请求=asyncResult.AsyncState作为HttpWebRequest;
尝试
{
HttpWebResponse=(HttpWebResponse)request.EndGetResponse(asyncResult);
字符串responseText=this.GetTextFromResponse(response);//不包括在内
foreach(在这个.regex.Matches(responseText)中匹配)
{
urinewuri=newUri(response.ResponseUri,match.Value);
锁(this.uriCollectionSync)
{
如果(!this.uriCollection.Contains(newUri))
{
this.uriCollection.Add(newUri);
这是StartUriCrawl(newUri);
}
}
}
}
捕获(WebException异常)
{
//处理异常
}
最后
{
if(互锁减量(参考此pendingRequests)==0)
{
this.crawlpletedevent.Set();
}
}
}
}

您可以查看CTP,它会让您更简单。您正在做的事情可以分为“任务”、块或工作单元,如果您提供任务,TPL可以为您并行化。它在内部也使用线程池,但更易于使用,并附带许多选项,如等待所有任务完成。查看Channel9视频,其中解释了各种可能性,并展示了并行递归遍历树的演示,这似乎非常适用于您的问题


但是,它仍然是一个预览版,在.NET 4.0之前不会发布,因此它没有保修,您必须手动将提供的System.Threading.dll(位于安装文件夹中)包含到您的项目中,我不知道您是否可以选择。

这看起来确实很有趣Julian。我将不能在这个实现中使用它,但我将研究它。谢谢:)这看起来很有趣,谢谢。我希望周末有机会尝试一下。我真不敢相信我忘记了异步请求的:o