none
Проблемы с BitmapImage в пуле потоков C#. RRS feed

  • Вопрос

  • Проблемы с BitmapImage  в пуле потоков C#.
    Здравствуйте!
    Есть программа для пакетного изменения размеров фотографий, берет фотки с одной папки, пережимает их и сохраняет в другую. Пока процедура работает в обычном цикле все замечательно, но при попытке использовать ее в пуле потоков выполнение встает на точке bi.EndInit().
    Сама процедура обработки фотографий:
           private static  void ResizePhotoWPF(object instance)
           {
               // Получаем путь до обрабатываемой фотографии
               FileInfo f = (FileInfo)instance;
    
               int width = 1200;  // Ширина картинки
    
               BitmapImage bi = new BitmapImage();
    
               bi.BeginInit();
               Uri mUri = new Uri(f.FullName, UriKind.RelativeOrAbsolute);
               bi.UriSource = mUri;
               bi.DecodePixelWidth = width;
               bi.EndInit();
    
               System.Drawing.Bitmap oB_out = null;
               BitmapEncoder enc = new JpegBitmapEncoder();
               System.Drawing.Bitmap oB = null;
    
               using (MemoryStream ms = new MemoryStream())
               {
                   enc.Frames.Add(BitmapFrame.Create(bi));
                   enc.Save(ms);
                   oB = new System.Drawing.Bitmap(ms);
                   oB_out = new System.Drawing.Bitmap(oB);
               }
    
               oB_out.Save(OutputFolder + "\\" + f.Name, System.Drawing.Imaging.ImageFormat.Jpeg);
               
               lock (workerLocker)
               {
                   runningWorkers--;
                   Monitor.Pulse(workerLocker);
               }
           }

    Пул потоков создается в отдельном BackgroundWorker:

          private void bw_DoWork(object sender, DoWorkEventArgs e)
           {
               BackgroundWorker worker = sender as BackgroundWorker;
    
               int MaxThreadsCount = Environment.ProcessorCount * 2;
               ThreadPool.SetMaxThreads(MaxThreadsCount, MaxThreadsCount);
               ThreadPool.SetMinThreads(2, 2);
    
               DirectoryInfo FolderInputInfo = new DirectoryInfo(InputFolder);
               FileInfo[] Photos = FolderInputInfo.GetFiles("*.jpg");
    
               runningWorkers = Photos.Length;
    
               foreach (FileInfo f in Photos)
               {
                   ThreadPool.QueueUserWorkItem(ResizePhotoWPF, f);
               }
    
               lock (workerLocker)
                   while (runningWorkers > 0)
                   {
                       if (worker.CancellationPending == true)
                       {
                           e.Cancel = true;
                           Start = false;
                           runningWorkers = 0;
                           break;
                       }
                       else
                       {
                           Monitor.Wait(workerLocker);
                           worker.ReportProgress(runningWorkers);
                       }
                   }
           }
    В чем может быть проблема?

    Исходники проекта - http://files.mail.ru/2212EA470B6740DBA0B03B241E9A0A85

    • Изменено GT-Volk 28 января 2013 г. 11:18 Добавлены исходники
    28 января 2013 г. 8:59

Ответы

  • Сейчас заметил особенность, код не работает до тех пор пока количество фотографий для обработки превышает количество максимально разрешенных потоков пула. То есть, если значение SetMaxThreads будет к примеру 16
    ThreadPool.SetMaxThreads(16, 16);
    а количество фотографий в выбранном каталоге будет 15, то код замечательно отработает. Но если добавить пару фотографий что бы их стало например 17, код выполнятся перестает.
    • Помечено в качестве ответа Abolmasov Dmitry 31 января 2013 г. 10:46
    28 января 2013 г. 11:39

Все ответы

  • А ваша программа имеет пользовательский интерфейс?
    28 января 2013 г. 10:03
  • Что значит "встает"? Зависает и не реагирует ни на какие действия или валиться с ошибкой?

    28 января 2013 г. 10:51
    Отвечающий
  • Да имеет. Вот в этой части ожидается либо отмена пользователем и отображается прогресс выполнения:

               lock (workerLocker)
                   while (runningWorkers > 0)
                   {
                       if (worker.CancellationPending == true)
                       {
                           e.Cancel = true;
                           Start = false;
                           runningWorkers = 0;
                           break;
                       }
                       else
                       {
                           Monitor.Wait(workerLocker);
                           worker.ReportProgress(runningWorkers);
                       }
                   }


    • Изменено GT-Volk 28 января 2013 г. 11:00
    28 января 2013 г. 10:59
  • Нет ошибок никаких нет. Выполнение кода во всех потоках доходит до bi.EndInit(); и дальше не идет, т.е. следующие строчки не выполняются. Но если ту же самую функцию вызвать не в пуле потоков а в потоке главного окна или в потоке BackgroundWorker, код работает и на выходе создается урезанное изображение.
    28 января 2013 г. 11:05
  • В моей практике как-то был подобный случай, в итоге - инициализацию BitmapImage пришлось проводить с использованием объекта Dispatcher.

    ImageSourceHelper.cs

    public static ImageSource GetImageSource(Stream stream, Dispatcher dispatcher)
            {
                if (stream == null) return null;
                BitmapImage bitmapImage = null;
                BackgroundHelper.DoWithDispatcher(dispatcher, () =>
                {
                    try
                    {
                        bitmapImage = new BitmapImage();
                        bitmapImage.BeginInit();
                        bitmapImage.StreamSource = stream;
                        bitmapImage.EndInit();
                    }
                    catch
                    {
                        bitmapImage = null;
                    }
    
                });
                return bitmapImage;
            }

    BackgroundHelper.cs

    public static void DoWithDispatcher(Dispatcher dispatcher, Action action)
            {
                if (dispatcher.CheckAccess())
                {
                    action();
                }
                else
                {
                    var done = new AutoResetEvent(false);
                    dispatcher.BeginInvoke((Action)delegate()
                    {
                        action();
                        done.Set();
                    });
                    done.WaitOne();
                }
            }

    Использовать следующим способом: ImageSource  imageSource = ImageSourceHelper. GetImageSource (stream, Application.Current.Dispatcher);

    28 января 2013 г. 11:18
  • Сейчас заметил особенность, код не работает до тех пор пока количество фотографий для обработки превышает количество максимально разрешенных потоков пула. То есть, если значение SetMaxThreads будет к примеру 16
    ThreadPool.SetMaxThreads(16, 16);
    а количество фотографий в выбранном каталоге будет 15, то код замечательно отработает. Но если добавить пару фотографий что бы их стало например 17, код выполнятся перестает.
    • Помечено в качестве ответа Abolmasov Dmitry 31 января 2013 г. 10:46
    28 января 2013 г. 11:39
  • Ну если ошибка в этом, то можно сделать некий диспетчер, который будет обрабатывать изображение в ограниченное количество потоков.
    28 января 2013 г. 11:55
  • Привет

    Очень странная ошибка, не должно быть такого поведения. Попробуйте если есть возможность выполнить эту задачу через Task-и.

    ThreadPool должен сам разбираться с потоками и не останавливать свое выполнение.


    Для связи [mail]

    30 января 2013 г. 8:23