C# 自定义控件锁定表单UI中需要调用

C# 自定义控件锁定表单UI中需要调用,c#,multithreading,C#,Multithreading,我有一个自定义控件。我正在更改它的图像不透明度,然后加载其他图像。我在控件中使用invokererequired,它按预期工作。 但是,当我在窗体上添加控件时,直到我的不透明度更改完成,窗体才会响应。 尽管我选中了invokererequired,为什么我的控件要锁定表单 我的项目提交代码 我的自定义控件代码 void ChangeImageOpacity(int opacity,string image_path) { if (this.InvokeRequired)

我有一个自定义控件。我正在更改它的图像不透明度,然后加载其他图像。我在控件中使用
invokererequired
,它按预期工作。 但是,当我在窗体上添加控件时,直到我的不透明度更改完成,窗体才会响应。 尽管我选中了
invokererequired
,为什么我的控件要锁定表单

我的项目提交代码

我的自定义控件代码

void ChangeImageOpacity(int opacity,string image_path)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new Action<int, string>(ChangeImageOpacity), new object[] { opacity, image_path });
        }
        else
        {
            this.Image = ImageOperation.ChangeImageOpacity(image_path, opacity);
            Application.DoEvents();
        }
    }



 void ChangeImageOpacityStarter(PictureBoxMode mode)
    {
        if(mode==PictureBoxMode.OPENED)
        {                
            for (int i = 80; i >= 0; i-=5)
            {
                ChangeImageOpacity(i, string.Format(@"icons\{0}.png",0));
                Thread.Sleep(20);
            }

            for (int i = 0; i < 80; i += 5)
            {
                ChangeImageOpacity(i, string.Format(@"icons\{0}.png", this.ImageID));
                Thread.Sleep(20);
            }
        }
        else if(mode==PictureBoxMode.OPENED_TO_CLOSING)
        {
            for (int i = 100; i >= 0; i--)
            {
                ChangeImageOpacity(i, string.Format(@"icons\{0}.png", this.imageID));
            }
            for (int i = 0; i < 100; i++)
            {
                ChangeImageOpacity(i, string.Format(@"icons\{0}.png", 0));
            }
        }
        else if(mode==PictureBoxMode.DESTROY)
        {
            for (int i = 100; i >= 0; i--)
            {
                ChangeImageOpacity(i, string.Format(@"icons\{0}.png", this.imageID));
            }
        }

    }
void ChangeImageOpacity(整数不透明度,字符串图像\u路径)
{
if(this.invokererequired)
{
Invoke(新操作(ChangeImageOpacity),新对象[]{opacity,image_path});
}
其他的
{
this.Image=ImageOperation.ChangeImageOpacity(图像路径,不透明度);
Application.DoEvents();
}
}
void ChangeImageOpacityStarter(PictureBoxMode模式)
{
如果(模式==PictureBoxMode.OPENED)
{                
对于(int i=80;i>=0;i-=5)
{
ChangeImageOpacity(i,string.Format(@“icons\{0}.png”,0));
睡眠(20);
}
对于(int i=0;i<80;i+=5)
{
ChangeImageOpacity(i,string.Format(@“icons\{0}.png”,this.ImageID));
睡眠(20);
}
}
else if(mode==PictureBoxMode.OPENED\u TO\u CLOSING)
{
对于(int i=100;i>=0;i--)
{
ChangeImageOpacity(i,string.Format(@“icons\{0}.png”,this.imageID));
}
对于(int i=0;i<100;i++)
{
ChangeImageOpacity(i,string.Format(@“icons\{0}.png”,0));
}
}
else if(mode==PictureBoxMode.DESTROY)
{
对于(int i=100;i>=0;i--)
{
ChangeImageOpacity(i,string.Format(@“icons\{0}.png”,this.imageID));
}
}
}

将Invoke更改为BeginInvoke,以便它不会锁定您的UI:

void ChangeImageOpacity(int opacity,string image_path)
{
    if (this.InvokeRequired)
    {
        this.BeginInvoke(new Action<int, string>(ChangeImageOpacity), new object[] { opacity, image_path });
    }
    else
    {
        this.Image = ImageOperation.ChangeImageOpacity(image_path, opacity);
        Application.DoEvents();
    }
}
void ChangeImageOpacity(整数不透明度,字符串图像\u路径)
{
if(this.invokererequired)
{
this.BeginInvoke(新操作(ChangeImageOpacity),新对象[]{opacity,image_path});
}
其他的
{
this.Image=ImageOperation.ChangeImageOpacity(图像路径,不透明度);
Application.DoEvents();
}
}

这应该是显而易见的。在执行不透明动画时,您将占用UI线程,并阻止它。一个线程在同一时间只能做一件事,所以要做除动画以外的任何事情,您需要暂时放弃工作,以允许其他UI操作发生

您的应用程序也会显示为“无响应”,在无效时不会重新呈现,对吗?在旧窗口上,您甚至无法移动窗口。同样,原因是相同的-您没有抽取消息队列,因此无法处理诸如
WM_PAINT
WM_MOVE
之类的windows消息

解决方案是确保在制作动画时没有阻塞UI。这是很棘手的,尤其是如果你想让它看起来平滑。您可以使用
wait Task.Delay
而不是
Thread.Sleep
,或计时器,或
后台工作人员

作为一个非常粗糙的解决方案,您可以在每个
线程之后执行
应用程序.DoEvents
。Sleep
(或
ChangeImageOpacity
),这有点类似于使用
Wait
,允许抽取和执行消息队列。不过,它还允许代码执行交错,这可能非常难以安全处理


您似乎不太了解
Invoke
InvokeRequired
是如何工作的-这是很明显的,因为您首先使用的是
InvokeRequired
:)
Invoke
只是将要在UI线程上执行的操作排队,然后等待它完成执行。无论您是从不同的线程执行(因此
invokererequired
true
并调用
Invoke
)还是不执行(因此
invokererequired
false
并直接调用该方法),最终结果都是在休眠1600ms的UI线程上执行代码,做一些额外的CPU工作来引导。在它完成执行之前,表单不会发生任何其他事情(除了Windows现在所做的使内容看起来更具响应性(即使编码错误)。

使用这个。BeginInvoke?ImageOperation.ChangeImageOpacity做什么?这是一个耗时的过程吗?还有,为什么要使用
DoEvents
Sleep
?它正在将picturebox当前图像更改为新图像。在当前图像更改期间,我增加了一个图像不透明度。我正在这样的游戏中编写一个图像匹配()在类似代码MyPictureBox=sender as MyPictureBox的控件上添加控件;box.BoxMode=PictureBoxMode.OPENED;是的,你说得对。它已成功渲染,但表单无法移动。当我在自定义控件中使用begininvoke时,我在想这足以避免交叉线程异常。所以它可以是izolated uıthread,但它是以我的形式创建的,并添加到tableadapter中。所以这取决于uıthread。我说得对吗?@IbrahimArac是的,除了UI线程之外,你不能从任何地方更新UI——这包括将数据绑定到数据表之类的东西;如果更改数据表导致UI更改,则必须从UI线程执行。这是一个很好的理由,你真的想在UI和你正在做的任何事情之间进行很多解耦。我正在与一个项目合作,该项目将UI和非UI工作进行这种特殊的混合,修复这种随意多线程产生的所有(被忽略的)错误是一件非常痛苦的事情。