【问题标题】:How to detect completion with unknown concurrent Task pushing & pulling ConcurrentQueue<T>如何检测未知并发任务推送和拉取 ConcurrentQueue<T> 的完成情况
【发布时间】:2012-10-16 23:37:07
【问题描述】:

几天前,我尝试在我的磁盘上执行快速搜索,但做了一些事情,例如属性、扩展、执行文件内部更改等......

这个想法是让它几乎没有限制/锁定,以避免大文件或目录中包含大量文件等的“延迟”...... 我知道“最佳实践”还很遥远,因为我没有使用诸如“MaxDegreeOfParallelism”之类的东西或带有“while(true)”的拉循环

尽管如此,代码运行得相当快,因为​​我们有支持它的架构。

如果有人想检查发生了什么,我尝试将代码转移到一个虚拟控制台项目。

class Program
{
    static ConcurrentQueue<String> dirToCheck;
    static ConcurrentQueue<String> fileToCheck;
    static int fileCount; //

    static void Main(string[] args)
    {
        Initialize();

        Task.Factory.StartNew(() => ScanDirectories(), TaskCreationOptions.LongRunning);
        Task.Factory.StartNew(() => ScanFiles(), TaskCreationOptions.LongRunning);

        Console.ReadLine();
    }

    static void Initialize()
    {
        //Instantiate caches
        dirToCheck = new ConcurrentQueue<string>();
        fileToCheck = new ConcurrentQueue<string>();

        //Enqueue Directory to Scan here
        //Avoid to Enqueue Nested/Sub directories, else they are going to be dcan at least twice
        dirToCheck.Enqueue(@"C:\");

        //Initialize counters
        fileCount = 0;
    }

    static void ScanDirectories()
    {
        String dirToScan = null;

        while (true)
        {
            if (dirToCheck.TryDequeue(out dirToScan))
            {
                ExtractDirectories(dirToScan);
                ExtractFiles(dirToScan);
            }

            //Just here as a visual tracker to have some kind an idea about what's going on and where's the load
            Console.WriteLine(dirToCheck.Count + "\t\t" + fileToCheck.Count + "\t\t" + fileCount);
        }
    }

    static void ScanFiles()
    {
        while (true)
        {
            String fileToScan = null;
            if (fileToCheck.TryDequeue(out fileToScan))
            {
                CheckFileAsync(fileToScan);
            }
        }
    }

    private static Task ExtractDirectories(string dirToScan)
    {
        Task worker = Task.Factory.StartNew(() =>
        {
            try
            {
                Parallel.ForEach<String>(Directory.EnumerateDirectories(dirToScan), (dirPath) =>
                {
                    dirToCheck.Enqueue(dirPath);
                });

            }
            catch (UnauthorizedAccessException) { }
        }, TaskCreationOptions.AttachedToParent);

        return worker;
    }

    private static Task ExtractFiles(string dirToScan)
    {
        Task worker = Task.Factory.StartNew(() =>
        {
            try
            {
                Parallel.ForEach<String>(Directory.EnumerateFiles(dirToScan), (filePath) =>
                {
                    fileToCheck.Enqueue(filePath);
                });
            }
            catch (UnauthorizedAccessException) { }
        }, TaskCreationOptions.AttachedToParent);

        return worker;
    }

    static Task CheckFileAsync(String filePath)
    {
        Task worker = Task.Factory.StartNew(() =>
        {
            //Add statement to play along with the file here
            Interlocked.Increment(ref fileCount);


            //WARNING !!! If your file fullname is too long this code may not be executed or may just crash
            //I just put a simple check 'cause i found 2 or 3 different error message between the framework & msdn documentation
            //"Full paths must not exceed 260 characters to maintain compatibility with Windows operating systems. For more information about this restriction, see the entry Long Paths in .NET in the BCL Team blog"
            if (filePath.Length > 260)
                return;
            FileInfo fi = new FileInfo(filePath);

            //Add statement here to use FileInfo

        }, TaskCreationOptions.AttachedToParent);

        return worker;
    }
}

问题: 我怎样才能检测到我已经完成了 ScanDirectory? 完成后,我可以设法将一个空字符串或其他任何内容排入文件队列,以退出它。 我知道如果我使用“AttachedToParent”,我可以在父任务上有一个完成状态,然后例如做一些类似“ContinueWith(()=> { /SomeCode to notice end/} )" 但是父任务仍然在做拉动并且陷入一种无限循环并且每个子语句都开始新的任务。

另一方面,我不能简单地测试每个队列中的“计数”,因为我可能会刷新文件列表和目录列表,但可能还有另一个任务会调用“EnumerateDirectory()”。

我正在尝试找到某种“反应式”解决方案,并避免在循环中出现一些“if()”,因为它是一个简单的带有 AsyncCall 的 while(true){},因此 80% 的时间都会被检查。

PS:我知道我可以使用 TPL 数据流,我不是因为我被困在 .net 4.0 上,无论如何,在没有数据流的 .net 4.5 中,因为 TPL 几乎没有改进,我仍然很好奇关于它

【问题讨论】:

    标签: c# .net concurrency task task-parallel-library


    【解决方案1】:

    你可以用BlockingCollection&lt;T&gt;.代替ConcurrentQueue&lt;T&gt;

    BlockingCollection&lt;T&gt; 是专门为此类生产者/消费者场景设计的,并提供了一个CompleteAdding 方法,以便生产者可以通知消费者它已完成添加工作。

    【讨论】:

    • 如果我没记错的话,这里的消费者也是生产者,不是吗?无论如何,谢谢您的建议,我会检查并尝试并让您知道
    • @AlexandreHgs 可能 - 虽然有点混乱 - 我看不出您在代码中将项目排入队列的位置。话虽如此,这很好——你可以有多个生产者和多个消费者,它们可以是“相同的”线程/进程。
    • 我看了看里面,我知道它可以帮助我摆脱第二个拉循环,用类似的东西代替 while (dirToCheck.IsCompleted) 这样我将退出第一个循环,仍然是我的问题是我如何检测如何检测我应该调用“dirToCheck.CompleteAdding()”,因为如果您检查函数“ExtractDirectories”,它会为 cirtoCheck 集合生成项目,但很有可能还有其他任务在做同样的事情随时行动。我发现的一种方法是为我的任务创建一种数组并使用一些 .WaitAll(taskArray),但我根本不是粉丝。
    • 所以现在只是通知您,也许是我,但我认为这没有帮助,更改集合类型并没有让我找到摆脱目录扫描循环的解决方案。我尝试了一种“一种”递归方式,使用“attachedToParent”选项和 .ContinueWith() 调用相同的方法,以确保所有孩子都完成了,但我遇到了巨大的性能问题:(
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-08
    • 2019-10-03
    • 2020-09-07
    相关资源
    最近更新 更多