并行化GDI +图像大小调整.net

PTa*_*PTa 15 .net multithreading jpeg gdi+ resize

我试图使用.Net并行调整jpegs的大小.我的所有尝试都失败了,因为Graphics.DrawImage-func似乎在激活时锁定.尝试以下剪切:

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
Run Code Online (Sandbox Code Playgroud)

所有线程都在graph.DrawImage()处等待.有没有办法加快使用其他功能的代码性能?是不是可以使用多线程的Graphics.Draw?在实际应用中,应该同时调整多个图像的大小(在四核PC上),并不总是相同.发布的代码仅用于测试目的......

提前致谢

编辑:根据评论更新代码

Fas*_*tAl 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
Run Code Online (Sandbox Code Playgroud)

  • 我将接受这个建议。迟早使用多线程进程内GDI操作,您将遇到死锁或完全随机的故障。在将一个示例升级到Microsoft PSS之后,他们承认由于某些竞争条件而导致堆栈/堆损坏,但没有真正的解决方案。如果要并行化(或使用线程安全的第三方解决方案),请坚持进行进程外转换! (2认同)

Bri*_*eon 5

我不确定为什么执行Graphics.DrawImage似乎对您进行了序列化,但我实际上注意到了您将工作项排队的一般模式的竞争条件。比赛是在WaitOne和之间Set。第一个工作项可能Set在任何其他工作项还没有排队之前就完成了。这将导致WaitOne在所有工作项目完成之前立即返回。

解决方案是将主线程视为工作项。pRuns在排队开始前递增一次,然后在排队完成后递减并用信号通知等待句柄,就像在正常工作项中一样。但是,更好的方法是使用CountdownEvent该类(如果您可以使用该类),因为它可以简化代码。幸运的是,我最近刚刚在另一个问题中发布了该模式


Ala*_*lan 5

如果您不介意 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();
    }
 }
Run Code Online (Sandbox Code Playgroud)