2010-09-15 73 views
15

我試圖使用.Net並行化jpeg的大小調整。我的所有嘗試都失敗了,因爲Graphics.DrawImage-func似乎在激活時鎖定。嘗試以下內容:並行GDI +圖像調整大小.net

Sub Main() 
    Dim files As String() = IO.Directory.GetFiles("D:\TEMP") 
    Dim imgs(25) As Image 
    For i As Integer = 0 To 25 
     imgs(i) = Image.FromFile(files(i)) 
    Next 

    Console.WriteLine("Ready to proceed ") 
    Console.ReadLine() 

    pRuns = 1 
    For i As Integer = 0 To 25 
     Threading.Interlocked.Increment(pRuns) 
     Threading.ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf LongTerm), imgs(i)) 
    Next 
    Threading.Interlocked.Decrement(pRuns) 

    pSema.WaitOne() 
    Console.WriteLine("Fin") 
    Console.ReadLine() 
    End Sub 

    Sub LongTerm(ByVal state As Object) 
    Dim newImageHeight As Integer 
    Dim oldImage As Image = CType(state, Image) 
    Dim newImage As Image 
    Dim graph As Graphics 
    Dim rect As Rectangle 
    Dim stream As New IO.MemoryStream 

    Try 
     newImageHeight = Convert.ToInt32(850 * oldImage.Height/oldImage.Width) 
     newImage = New Bitmap(850, newImageHeight, oldImage.PixelFormat) 
     graph = Graphics.FromImage(newImage) 
     rect = New Rectangle(0, 0, 850, newImageHeight) 

     With graph 
     .CompositingQuality = Drawing2D.CompositingQuality.HighQuality 
     .SmoothingMode = Drawing2D.SmoothingMode.HighQuality 
     .InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic 
     End With 

     'Save image to memory stream 
     graph.DrawImage(oldImage, rect) 
     newImage.Save(stream, Imaging.ImageFormat.Jpeg) 
    Catch ex As Exception 

    Finally 
     If graph IsNot Nothing Then 
     graph.Dispose() 
     End If 
     If newImage IsNot Nothing Then 
     newImage.Dispose() 
     End If 
     oldImage.Dispose() 
     stream.Dispose() 

     Console.WriteLine("JobDone {0} {1}", pRuns, Threading.Thread.CurrentThread.ManagedThreadId) 
     Threading.Interlocked.Decrement(pRuns) 
     If pRuns = 0 Then 
     pSema.Set() 
     End If 
    End Try 

    End Sub 

所有線程都在graph.DrawImage()中等待。有沒有使用其他函數來加速代碼性能的方法?是不可能使用Graphics.Draw與多個線程?在實際應用中,多個圖像應該同時調整大小(在四核電腦上),但並不總是相同的。張貼的代碼僅用於測試目的...

在此先感謝

編輯:更新的代碼根據意見

+0

如果爲pSema和pRuns添加聲明,則剪切並粘貼和測試proggy的答案將更容易。 – FastAl 2010-09-22 20:58:40

+0

我發現很多時間都花在IO上,而不是在DrawImage調用中。如果你傳遞一個流的Image構造函數,你將避免解碼器中的一些鎖定問題。 (而且速度稍快)。我在構建[imageresizing.net庫](http://imageresizing.net)時運行了一些基準測試。 – 2011-05-18 18:14:03

+0

此外,WS2008 R2和Win7上的WIC是一個不錯的選擇,而http://imageresizing.net/支持將該代碼路徑作爲一組插件。 WPF在服務器上仍然是一個壞主意,太多未修補的內存泄漏。 – 2012-01-07 07:45:06

回答

17

用途。

GDI +爲每個進程阻塞很多方法。是的,這是一種痛苦,但是沒有辦法。幸運的是,像這樣的任務(以及任何在文件系統上處理文件的任務),將多個進程之間的工作負載分開太容易了。幸運的是,它看起來像GDI +使用鎖,而不是互斥鎖,所以它是併發的!

我們有一些圖形程序可以用來處理圖像。一位程序員在轉換程序中啓動6-7個副本,正在製作中。所以它不是混亂的,相信我。哈克?你沒有得到報酬,看起來很漂亮。把事做好!

便宜例子(注意:這不會在IDE中工作,建立並運行的EXE):

Imports System.Drawing 
Module Module1 
    Dim CPUs As Integer = Environment.ProcessorCount 

    Dim pRuns As New System.Collections.Generic.List(Of Process) 

    Sub Main() 
     Dim ts As Date = Now 
     Try 
      If Environment.GetCommandLineArgs.Length > 1 Then 
       LongTerm(Environment.GetCommandLineArgs(1)) 
       Exit Sub 
      End If 

      Dim i As Integer = 0 
      Dim files As String() = IO.Directory.GetFiles("D:\TEMP", "*.jpg") 
      Dim MAX As Integer = Math.Min(26, files.Count) 
      While pRuns.Count > 0 Or i < MAX 

       System.Threading.Thread.Sleep(100) 

       If pRuns.Count < CInt(CPUs * 1.5) And i < MAX Then ''// x2 = assume I/O has low CPU load 
        Console.WriteLine("Starting process pRuns.count = " & pRuns.Count & " for " & files(i) & " path " & _ 
             Environment.GetCommandLineArgs(0)) 
        Dim p As Process = Process.Start(Environment.GetCommandLineArgs(0), """" & files(i) & """") 
        pRuns.Add(p) 
        i += 1 
       End If 

       Dim i2 As Integer 
       i2 = 0 
       While i2 < pRuns.Count 
        If pRuns(i2).HasExited Then 
         pRuns.RemoveAt(i2) 
        End If 
        i2 += 1 
       End While 


      End While 
     Catch ex As Exception 
      Console.WriteLine("Blew up." & ex.ToString) 
     End Try 
     Console.WriteLine("Done, press enter. " & Now.Subtract(ts).TotalMilliseconds) 
     Console.ReadLine() 
    End Sub 


    Sub LongTerm(ByVal file As String) 
     Try 
      Dim newImageHeight As Integer 
      Dim oldImage As Image 
      Console.WriteLine("Reading " & CStr(file)) 
      oldImage = Image.FromFile(CStr(file)) 
      Dim rect As Rectangle 

      newImageHeight = Convert.ToInt32(850 * oldImage.Height/oldImage.Width) 
      Using newImage As New Bitmap(850, newImageHeight, oldImage.PixelFormat) 
       Using graph As Graphics = Graphics.FromImage(newImage) 
        rect = New Rectangle(0, 0, 850, newImageHeight) 

        With graph 
         .CompositingQuality = Drawing2D.CompositingQuality.HighQuality 
         .SmoothingMode = Drawing2D.SmoothingMode.HighQuality 
         .InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic 
        End With 

        Console.WriteLine("Converting " & CStr(file)) 
        graph.DrawImage(oldImage, rect) 

        Console.WriteLine("Saving " & CStr(file)) 
        newImage.Save("d:\temp\Resized\" & _ 
            IO.Path.GetFileNameWithoutExtension(CStr(file)) & ".JPG", _ 
            System.Drawing.Imaging.ImageFormat.Jpeg) 
       End Using 
      End Using 
     Catch ex As Exception 
      Console.WriteLine("Blew up on " & CStr(file) & vbCrLf & ex.ToString) 
      Console.WriteLine("Press enter") 
      Console.ReadLine() 
     End Try 
    End Sub 

End Module 
+1

我會繼續這個建議。遲早,在多線程進程中的GDI操作中,您會遇到死鎖或完全隨機故障。在將一個示例升級到Microsoft PSS之後,他們承認由於某些競爭條件而導致堆棧/堆棧損壞,但沒有真正的解決方案。如果要並行化(或使用線程安全的第三方解決方案),請堅持使用進程外轉換! – 2010-09-23 03:53:07

+1

難道你不應該在完成這些過程時處理這些過程嗎? – notJim 2010-09-25 00:03:59

+1

回覆:「GDI +每個進程阻塞很多方法。」_今天這仍然是真的嗎? [MSDN博客文章](http://blogs.msdn.com/b/e7/archive/2009/04/25/engineering-windows-7-for-graphics-performance.aspx「'工程Windows 7圖形性能'通過史蒂文Sinofsky「)會導致我相信,這已被固定(或至少改善)在Windows 7 – stakx 2015-05-15 14:26:43

4

我不知道爲什麼Graphics.DrawImage執行似乎序列化你,但我實際上注意到您的排隊條件與您排隊工作項目的一般模式。比賽介於WaitOneSet之間。第一個工作項目有可能在Set之前,其他任何人都已排隊。這將導致WaitOne在所有工作項目完成之前立即返回。

解決方案是將主線程看作是工作項目。在排隊開始前增加pRuns一次,然後遞減並在排隊完成後發出等待處理信號,就像您在正常工作項目中一樣。然而,更好的方法是使用CountdownEvent類,如果這對您是可用的,因爲它簡化了代碼。幸運的是,它會有I just recently posted the pattern in another question

+0

我無法使用CountdownEvent類。但是(如果主線程睡得足夠長)可能有可能,函數中的if語句從來都不是真的,不是嗎?你有一個想法如何解決這個問題? – PTa 2010-09-15 19:19:32

+1

@PTa:不,你在循環之前遞增'pRuns',然後在循環之後遞減它......在調用'WaitOne'之前。所有的「Increment」和「Decrement」調用應該平衡。有人最終會將其減至0.它可能是一個工作項目,或者它可能是主線程。你其實已經擁有了大部分的正確性。 – 2010-09-15 19:27:08

+0

對不起 - 我在推理中出錯了! :) – PTa 2010-09-15 19:33:33

4

如果你不介意WPF的方法,這裏有一些嘗試。以下是一個簡單的重新縮放方法,它接受圖像流並生成一個包含結果JPEG數據的byte []。既然你不想用GDI +實際繪製圖像,我認爲這是適合你,儘管是基於WPF的。 (唯一的要求是在您的項目中引用WindowsBase和PresentationCore。)

優點包括更快的編碼(在我的機器上爲200-300%)和更好的並行加速,雖然我也在WPF渲染路徑中看到一些不需要的序列化。讓我知道這是如何爲你工作的。如果有必要,我相信它可以進一步優化。

代碼:

byte[] ResizeImage(Stream source) 
{ 
    BitmapFrame frame = BitmapFrame.Create(source, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None); 
    var newWidth = frame.PixelWidth >> 1; 
    var newHeight = frame.PixelHeight >> 1; 
    var rect = new Rect(new System.Windows.Size(newWidth, newHeight)); 
    var drawingVisual = new DrawingVisual(); 
    using (var drawingContext = drawingVisual.RenderOpen()) 
     drawingContext.DrawImage(frame, rect); 
    var resizedImage = new RenderTargetBitmap(newWidth, newHeight, 96.0, 96.0, PixelFormats.Default); 
    resizedImage.Render(drawingVisual); 
    frame = BitmapFrame.Create(resizedImage); 

    using (var ms = new MemoryStream()) 
    { 
     var encoder = new JpegBitmapEncoder(); 
     encoder.Frames.Add(frame); 
     encoder.Save(ms); 
     return ms.ToArray(); 
    } 
} 
0

比使用GDI +以外的圖像處理庫。

我們在一個相當高容量的網站上使用ImageMagick來調整上傳圖片的大小(上傳的圖片通常是10-40萬像素,但爲了能夠在網站上使用它們(在Flash模塊中),我們將它們調整爲最小尺寸爲1500像素)。處理速度非常快,並且效果出色。

我們目前使用命令行界面啓動一個新的ImageMagick進程。這在啓動新進程時會產生一些開銷,但由於映像非常大,通常是整個調整大小過程的一小段時間。它也可以在進程中使用ImageMagick,但從現在開始還沒有嘗試過。1.我們不需要它提供的額外性能,2.在其他進程中運行第三方軟件感覺很好。

當使用ImageMagick時,您還可以獲得許多其他可能性,例如更好的過濾和許多其他功能。