为什么后台线程中的图形操作会阻塞主 UI 线程中的图形操作?

2024-04-06

我有一个后台线程正在给定文件夹中创建图像的灰度缩略图。我看到的问题是后台线程中的 Graphics.DrawImage() 调用似乎以某种方式阻止了主 UI 线程上的 Graphics 操作。

我可能会误解我在这里看到的内容,并且直到今晚晚些时候才有机会进行任何深入的分析,尽管我不希望能够找到太多东西。

我试图想出尽可能小的重现案例。如果您将默认项目中的表单替换为下面的表单(并在文件夹中有一些图像进行测试),您会注意到动画标签在窗口中来回弹跳时会出现卡顿。然而,如果您取消顶部#define 的注释,以便子控件动画化而不是重绘窗口内容,则它运行得非常流畅。

任何人都可以看到我在这里做错了什么,或者帮助我弄清楚如何在更新循环期间避免这种口吃?

//#define USE_LABEL_CONTROL

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;

namespace ThreadTest
{
    public partial class Form1 : Form
    {
        private const string ImageFolder = "c:\\pics";
        private const string ImageType = "*.jpg";

        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnLoad(EventArgs e)
        {
            this.Size = new Size(300, 300);

            string[] ImageFiles = Directory.GetFiles(ImageFolder, 
                                                        ImageType, 
                                                        SearchOption.AllDirectories);

            // kick off a thread to create grayscale thumbnails of all images
            this.thumbnailThread = new Thread(this.thumbnailThreadFunc);
            this.thumbnailThread.Priority = ThreadPriority.Lowest;
            this.thumbnailThread.Start(ImageFiles);

            // set a timer to start us off...
            this.startTimer = new Timer();
            this.startTimer.Interval = 500;
            this.startTimer.Tick += this.startTimer_Tick;
            this.startTimer.Start();

#if USE_LABEL_CONTROL
            this.label.Location = this.labelRect.Location;
            this.label.Size = this.labelRect.Size;
            this.label.Text = "Loaded: 0";
            this.label.BorderStyle = BorderStyle.FixedSingle;
            this.Controls.Add(this.label);
#endif

            base.OnLoad(e);
        }

        void startTimer_Tick(object sender, EventArgs e)
        {
            // kill the timer
            this.startTimer.Stop();

            // update ourself in a loop
            while (this.IsHandleCreated)
            {
                int NextTick = Environment.TickCount + 50;

                // update the label position
                this.labelRect.Offset(this.currentLabelDirection, 0);
                if (this.labelRect.Right == this.ClientRectangle.Right ||
                    this.labelRect.Left == 0)
                {
                    this.currentLabelDirection = -this.currentLabelDirection;
                }

                // update the display
#if USE_LABEL_CONTROL
                this.label.Text = "Loaded: " + this.thumbs.Count;
                this.label.Location = this.labelRect.Location;
#else
                using (Graphics Dest = this.CreateGraphics())
                {
                    this.redrawControl(Dest, this.ClientRectangle);
                }
#endif

                Application.DoEvents();
                Thread.Sleep(Math.Max(0, NextTick - Environment.TickCount));
            }
        }

        private void thumbnailThreadFunc(object ThreadData)
        {
            string[] ImageFiles = (string[]) ThreadData;
            foreach (string ImageFile in ImageFiles)
            {
                if (!this.IsHandleCreated)
                {
                    return;
                }

                using (Image SrcImg = Image.FromFile(ImageFile))
                {
                    Rectangle SrcRect = new Rectangle(Point.Empty, SrcImg.Size);

                    Rectangle DstRect = new Rectangle(Point.Empty, new Size(300, 200));
                    Bitmap DstImg = new Bitmap(DstRect.Width, DstRect.Height);
                    using (Graphics Dst = Graphics.FromImage(DstImg))
                    {
                        using (ImageAttributes Attrib = new ImageAttributes())
                        {
                            Attrib.SetColorMatrix(this.grayScaleMatrix);
                            Dst.DrawImage(SrcImg, 
                                            DstRect, 
                                            0, 0, SrcRect.Width, SrcRect.Height, 
                                            GraphicsUnit.Pixel, 
                                            Attrib);
                        }
                    }

                    lock (this.thumbs)
                    {
                        this.thumbs.Add(DstImg);
                    }
                }
            }
        }

#if !USE_LABEL_CONTROL
        private void redrawControl (Graphics Dest, Rectangle UpdateRect)
        {
            Bitmap OffscreenImg = new Bitmap(this.ClientRectangle.Width, 
                                                this.ClientRectangle.Height);
            using (Graphics Offscreen = Graphics.FromImage(OffscreenImg))
            {
                Offscreen.FillRectangle(Brushes.White, this.ClientRectangle);
                Offscreen.DrawRectangle(Pens.Black, this.labelRect);
                Offscreen.DrawString("Loaded: " + this.thumbs.Count,
                                        SystemFonts.MenuFont,
                                        Brushes.Black,
                                        this.labelRect);
            }
            Dest.DrawImageUnscaled(OffscreenImg, 0, 0);
            OffscreenImg.Dispose();
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            return;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            this.redrawControl(e.Graphics, e.ClipRectangle);
        }
#endif


        private ColorMatrix grayScaleMatrix = new ColorMatrix(new float[][] 
                                                        {
                                                            new float[] {.3f, .3f, .3f, 0, 0},
                                                            new float[] {.59f, .59f, .59f, 0, 0},
                                                            new float[] {.11f, .11f, .11f, 0, 0},
                                                            new float[] {0, 0, 0, 1, 0},
                                                            new float[] {0, 0, 0, 0, 1}
                                                        });
        private Thread thumbnailThread;
        private Timer startTimer;
        private List<Bitmap> thumbs = new List<Bitmap>();
        private Label label = new Label();
        private int currentLabelDirection = 1;
        private Rectangle labelRect = new Rectangle(0, 125, 75, 20);
    }
}

事实证明,答案是使用多个进程来处理后台 GDI+ 任务。如果您在 VS2010 中的并发分析器下运行上述代码,您将看到前台线程阻塞在由后台线程中的 DrawImage() 调用保护的关键部分上。

该线程还讨论了这个问题,并指出由于它使用关键部分,锁将是每个进程的,并且后台任务可以使用多个进程而不是线程来并行化:

并行化 GDI+ 图像大小调整 .net https://stackoverflow.com/questions/3719748/parallelizing-gdi-image-resizing-net

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么后台线程中的图形操作会阻塞主 UI 线程中的图形操作? 的相关文章

  • Web UI 中的 .Result 出现死锁

    我正在阅读以下主题http blog stephencleary com 2012 07 dont block on async code html http blog stephencleary com 2012 07 dont bloc
  • 如何向 UWP 项目添加 .NET dll 引用?

    我有几个适用于 NETv4 x 的 NET dll 项目 我将版本更改为 4 6 1 并重新构建 没有出现问题 当我尝试从 UWP 项目向它们添加引用时 出现错误 项目的目标是 NETCore 而文件引用的目标是 NET框架 这不是受支持的
  • 处理 LINQ sum 表达式中的 null

    我正在使用 LINQ 查询来查找列的总和 并且在少数情况下该值有可能为空 我现在使用的查询是 int score dbContext domainmaps Where p gt p SchoolId schoolid Sum v gt v
  • 在 Windows Phone 上启动 pdf 文件时出现 System.Runtime.InteropServices.COMException

    我正在尝试使用我之前在另一个应用程序上使用过的以下工作代码打开 pdf 文件 但这一次 当流程到达此行时 我收到 System Runtime InteropServices COMException Windows System Laun
  • 使用 C# 使用应用程序密码登录 Office 365 SMTP

    在我们的 Office 365 公司帐户中实施两步身份验证之前 我的 C WPF 程序已成功进行身份验证并发送邮件 我使用了 SmtpClient 库 但现在我必须找到另一个解决方案 因为它不再起作用 我找不到任何使用 O365 应用程序密
  • 当我单击 GridView 项时返回 ImageView 实例

    当我点击GridView项时如何返回ImageView实例 我为 ItemClick 创建自定义绑定事件 public class ItemClickSquareBinding MvxBaseAndroidTargetBinding pri
  • System.InvalidCastException:指定的强制转换无效

    使用 WatiN 的自动化正在进行中 使用几个并发线程来测试应用程序 很少有线程失败 日志报告 堆栈跟踪显示以下内容 System InvalidCastException Specified cast is not valid at SH
  • 身份未映射异常

    System Security Principal IdentityNotMappedException 无法转换部分或全部身份引用 该错误仅在应用程序注册后出现一次 当 SecurityIdentifier 无法映射时 例如 返回 Ide
  • 字节到二进制字符串 C# - 显示所有 8 位数字

    我想在文本框中显示一个字节 现在我正在使用 Convert ToString MyVeryOwnByte 2 但是 当字节开头有 0 时 这些 0 就会被删除 例子 MyVeryOwnByte 00001110 Texbox shows g
  • __FUNCTION__ 宏的 C# 版本

    有人对 C FUNCTION 宏的 C 版本有好的解决方案吗 编译器似乎不喜欢它 尝试使用这个代替 System Reflection MethodBase GetCurrentMethod Name C 没有 LINE or FUNCTI
  • 对数字进行向上和向下舍入 C++

    我试图让我的程序分别向上和向下舍入数字 例如 如果数字是3 6 我的程序应该四舍五入最接近的数字 4 如果该数字是3 4 它将向下舍入为 3 我尝试使用ceil库获取 3 个项目的平均值 results ceil marks1 marks2
  • 捕获当前正在播放的声音

    是否可以捕获计算机上当前播放的声音 如果能够将其保存为 mp3 就好了 但我认为这样做会存在一些法律问题 所以 wav 也可以 我环顾四周 有人建议使用虚拟音频线之类的东西 在 C 中捕获声音输出 https stackoverflow c
  • 推送 Lua 表

    我已经创建了一个Lua表C 但我不知道如何将该表推入堆栈顶部 以便我可以将其传递给 Lua 函数 有谁知道如何做到这一点 这是我当前的代码 lua createtable state libraries size 0 int table i
  • 为什么以下代码不允许我使用 fgets 获取用户输入但可以使用 scanf?

    这是一个更大程序的简短摘录 但该程序的其余部分无关紧要 因为我认为我能够隔离该问题 我怀疑这与我使用 fgets 的方式有关 我读过 最好使用 fgets 而不是 scanf 但我似乎无法让它在这里正常工作 当我使用以下代码时 程序不会给我
  • 在 C# 中赋值后如何保留有关对象的信息?

    我一直在问我的想法可能是解决方案 https stackoverflow com questions 35254467 is it possible in c sharp to get the attributes attached to
  • 使用 Linq 进行异步Where过滤

    我有一个List通过填充的元素async调用 WebService 没问题 我需要过滤该列表以便在应用程序视图上显示某些内容 我试过这个 List
  • 标准 C 中的 sizeof 与 sizeof()? [复制]

    这个问题在这里已经有答案了 我看到一些直接使用 sizeof 的代码 想知道它是否是标准 C 令我惊讶的是 它运行得很好 这是一个例子 include
  • 如何在 C++ 中使用 PI 常数

    我想在一些 C 程序中使用 PI 常数和三角函数 我得到三角函数include
  • 通过 MSBuild 调用 cl.exe 时无限期挂起

    我正在尝试在我的 主要是 C 项目上运行 MSBuild 想象一下一个非常庞大的代码库 Visual Studio 2015 是有问题的工具集 Windows 7 SP1 和 VS 2015 更新 2 即使使用 m 1 从而迫使它仅使用一个
  • 在 C# 中读取/写入命令行程序

    我正在尝试与 C 的命令行程序进行对话 它是一个情绪分析器 它的工作原理如下 CMD gt java jar analyser jar gt Starting analyser 这是我想从我的 C 程序插入内容的地方 例如 I love y

随机推荐