سؤال

لقد بدأت للتو في استكشاف PTL ولدي سؤال تصميم.

السيناريو الخاص بي: لدي قائمة بعناوين 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;
}
هل كانت مفيدة؟

المحلول

عادةً ما تستخدم 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));
            }
        } 
    }
}

تحذير: لا يحتوي الرمز على أي خطأ في التحقق من خطأ أو إلغاء. إنه متأخر وتحتاج إلى شيء لفعله بشكل صحيح؟ قون

هذا مثال على نمط خط الأنابيب. يفترض أن الحصول على صورة بطيئة جدًا وأن تكلفة القفل داخل مجموعة الحظر لن تتسبب في مشكلة لأنها تحدث بشكل غير منتظم نسبيًا بالوقت الذي يقضيه تنزيل الصور.

كتابنا ... يمكنك قراءة المزيد حول هذا الموضوع والأنماط الأخرى للبرمجة المتوازية في http://paralledpatterns.codeplex.com/يغطي الفصل 7 خطوط الأنابيب ويظهر الأمثلة المصاحبة خطوط الأنابيب مع معالجة الأخطاء والإلغاء.

نصائح أخرى

يوفر TPL بالفعل وظيفة Continuewith لتنفيذ مهمة واحدة عند انتهاء آخر. تعتبر المهام واحدة من الأنماط الرئيسية المستخدمة في TPL للعمليات غير المتزامنة.

تقوم الطريقة التالية بتنزيل مجموعة من الصور وتستمر عن طريق إعادة تسمية كل من الملفات

static void DownloadInParallel(string[] urls)
{
   var tempFolder = Path.GetTempPath();

   var downloads = from url in urls
                   select Task.Factory.StartNew<string>(() =>{
                       using (var client = new WebClient())
                       {
                           var uri = new Uri(url);
                           string file = Path.Combine(tempFolder,uri.Segments.Last());
                           client.DownloadFile(uri, file);
                           return file;
                       }
                   },TaskCreationOptions.LongRunning|TaskCreationOptions.AttachedToParent)
                  .ContinueWith(t=>{
                       var filePath = t.Result;
                       File.Move(filePath, filePath + ".test");
                  },TaskContinuationOptions.ExecuteSynchronously);

    var results = downloads.ToArray();
    Task.WaitAll(results);
}

يجب عليك أيضًا التحقق من WebClient Async مهام من عينات الموازي extensionsextras. تعالج أساليب امتداد DownloadXXXXTASK كل من إنشاء المهام والتنزيل غير المتزامن للملفات.

تستخدم الطريقة التالية ملحق DownloadDatatask للحصول على بيانات الصورة وتدويرها قبل حفظها على القرص

static void DownloadInParallel2(string[] urls)
{
    var tempFolder = Path.GetTempPath();

    var downloads = from url in urls
         let uri=new Uri(url)
         let filePath=Path.Combine(tempFolder,uri.Segments.Last())
         select new WebClient().DownloadDataTask(uri)                                                        
         .ContinueWith(t=>{
            var img = Image.FromStream(new MemoryStream(t.Result));
            img.RotateFlip(RotateFlipType.RotateNoneFlipY);
            img.Save(filePath);
         },TaskContinuationOptions.ExecuteSynchronously);

    var results = downloads.ToArray();
    Task.WaitAll(results);
}

من المحتمل أن تكون أفضل طريقة للقيام بذلك من خلال تنفيذ نمط المراقب: اطلب RetreiveImages تنفيذ الوظيفة iobservable, ، ضع "إجراء الصورة المكتمل" في iobserver أشياء OnNext الطريقة ، واشتركها في RetreiveImages.

لم أجرب هذا بنفسي حتى الآن (لا يزال يتعين علي اللعب أكثر مع مكتبة المهام) ، لكنني أعتقد أن هذه هي الطريقة "الصحيحة" للقيام بذلك.

// قم بتنزيل جميع الصور

private async void GetAllImages ()
{
    var downloadTasks = listOfURLs.Where(url =>   !string.IsNullOrEmpty(url)).Select(async url =>
            {
                var ret = await RetrieveImage(url);
                return ret;
        }).ToArray();

        var counts = await Task.WhenAll(downloadTasks);
}

//From the Collector class
public async Task<Image> RetrieveImage(string url)
{
    var lambdaVar = url;  //Required... Bleh
    using (WebClient client = new WebClient())
    {
        //TODO: Replace with live image locations
        var fileName = String.Format("{0}.png", i);
        await client.DownloadFile(lambdaVar, Path.Combine(Application.StartupPath, fileName));
    }
    return Image.FromFile(Path.Combine(Application.StartupPath, fileName));
}  
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top