并行任务库WaitAny Design

col*_*ium 1 c# task-parallel-library

我刚开始探索PTL并有一个设计问题.

我的场景:我有一个URL列表,每个URL都引用一个图像.我希望每个图像并行下载.一旦下载了至少一个图像,我想执行一个对下载的图像执行某些操作的方法.该方法不应该并行化 - 它应该是串行的.

我认为以下内容可行,但我不确定这是否是正确的方法.因为我有收集图像的单独类以及对收集的图像做"某事",所以我最终传递了一系列任务,这似乎是错误的,因为它暴露了如何检索图像的内部工作方式.但我不知道如何解决这个问题.实际上,这两种方法都有更多,但这对此并不重要.只要知道它们真的不应该归结为一个大的方法,它既可以检索也可以对图像做一些事情.

//From the Director class
Task<Image>[] downloadTasks = collector.RetrieveImages(listOfURLs);

for (int i = 0; i < listOfURLs.Count; i++)
{
    //Wait for any of the remaining downloads to complete
    int completedIndex = Task<Image>.WaitAny(downloadTasks);
    Image completedImage = downloadTasks[completedIndex].Result;

    //Now do something with the image (this "something" must happen serially)
    //Uses the "Formatter" class to accomplish this let's say
}

///////////////////////////////////////////////////

//From the Collector class
public Task<Image>[] RetrieveImages(List<string> urls)
{
    Task<Image>[] tasks = new Task<Image>[urls.Count];

    int index = 0;
    foreach (string url in urls)
    {
        string lambdaVar = url;  //Required... Bleh
        tasks[index] = Task<Image>.Factory.StartNew(() =>
            {
                using (WebClient client = new WebClient())
                {
                    //TODO: Replace with live image locations
                    string fileName = String.Format("{0}.png", i);
                    client.DownloadFile(lambdaVar, Path.Combine(Application.StartupPath, fileName));
                }

                return Image.FromFile(Path.Combine(Application.StartupPath, fileName));
            },
            TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent);

        index++;
    }

    return tasks;
}
Run Code Online (Sandbox Code Playgroud)

Ade*_*ler 9

通常,当您不关心任何其他任务的结果时,您使用WaitAny等待一项任务.例如,如果你只是关心第一张被发现的图像.

怎么样呢.

这会创建两个任务,一个加载图像并将其添加到阻塞集合中.第二个任务等待集合并处理添加到队列的任何图像.加载所有图像后,第一个任务将关闭队列,以便第二个任务可以关闭.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class Class1
    {
        readonly string _path = Directory.GetCurrentDirectory();

        public void Demo()
        {
            IList<string> listOfUrls = new List<string>();
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/editicon.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/favorite-star-on.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/arrow_dsc_green.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/editicon.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/favorite-star-on.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/arrow_dsc_green.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/editicon.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/favorite-star-on.gif");
            listOfUrls.Add("http://i3.codeplex.com/Images/v16821/arrow_dsc_green.gif");

            BlockingCollection<Image> images = new BlockingCollection<Image>();

            Parallel.Invoke(
                () =>                   // Task 1: load the images
                {
                    Parallel.For(0, listOfUrls.Count, (i) =>
                        {
                            Image img = RetrieveImages(listOfUrls[i], i);
                            img.Tag = i;
                            images.Add(img);    // Add each image to the queue
                        });
                    images.CompleteAdding();    // Done with images.
                },
                () =>                   // Task 2: Process images serially
                {
                    foreach (var img in images.GetConsumingEnumerable())
                    {
                        string newPath = Path.Combine(_path, String.Format("{0}_rot.png", img.Tag));
                        Console.WriteLine("Rotating image {0}", img.Tag);
                        img.RotateFlip(RotateFlipType.RotateNoneFlipXY);

                        img.Save(newPath);
                    }
                });
        }

        public Image RetrieveImages(string url, int i)
        {
            using (WebClient client = new WebClient())
            {
                string fileName = Path.Combine(_path, String.Format("{0}.png", i));
                Console.WriteLine("Downloading {0}...", url);
                client.DownloadFile(url, Path.Combine(_path, fileName));
                Console.WriteLine("Saving {0} as {1}.", url, fileName);
                return Image.FromFile(Path.Combine(_path, fileName));
            }
        } 
    }
}
Run Code Online (Sandbox Code Playgroud)

警告:代码没有任何错误检查或取消.现在已经很晚了,你还需要做些什么吗?:)

这是管道模式的一个例子.它假设获取图像非常慢并且锁定集合内部锁定的成本不会导致问题,因为与下载图像所花费的时间相比,它发生的频率相对较低.

我们的书...您可以在http://parallelpatterns.codeplex.com/上阅读有关此和其他并行编程模式的更多信息. 第7章介绍了管道,附带的示例显示了具有错误处理和取消的管道.