为什么允许我在非 GUI 线程中加载图像?
Why am I allowed to load an image in the non-GUI thread?
众所周知,除了 GUI 线程之外,您不能使用任何其他线程更改 GUI。因此,一个常用的(我使用的)简单技巧是调用:
this.Invoke((MethodInvoker)delegate { pictureBox1.Visible = false; });
我正在构建我的程序并启动它,很快注意到我忘记在调用程序中放置一个 PictureBox.Load(string url)
,但是没有发生错误。
所以我很好奇,为什么不允许我这样做(在非 GUI 线程中):
pictureBox1.Visible = false; // eg.
但我可以这样做:
pictureBox1.Load(url); // url = link to image
当您加载新图像时,this is what happens 在 PictureBox
中(这是从 Load
方法中调用的):
private void InstallNewImage(Image value,
ImageInstallationType installationType)
{
StopAnimate();
this.image = value;
LayoutTransaction.DoLayoutIf(AutoSize, this, this, PropertyNames.Image);
Animate();
if (installationType != ImageInstallationType.ErrorOrInitial)
{
AdjustSize();
}
this.imageInstallationType = installationType;
Invalidate();
CommonProperties.xClearPreferredSizeCache(this);
}
所以你可以看到它真正做的就是设置图像然后调用 Invalidate
,它可以从其他线程调用。
Visible
(继承自 Control
),您可以在这里看到,它通过 p/invoke 做了很多事情并且必须在主线程 UI 上完成.
protected virtual void SetVisibleCore(bool value) {
try {
System.Internal.HandleCollector.SuspendCollect();
if (GetVisibleCore() != value) {
if (!value) {
SelectNextIfFocused();
}
bool fireChange = false;
if (GetTopLevel()) {
// The processing of WmShowWindow will set the visibility
// bit and call CreateControl()
//
if (IsHandleCreated || value) {
SafeNativeMethods.ShowWindow(new HandleRef(this, Handle), value ? ShowParams : NativeMethods.SW_HIDE);
}
}
else if (IsHandleCreated || value && parent != null && parent.Created) {
// We want to mark the control as visible so that CreateControl
// knows that we are going to be displayed... however in case
// an exception is thrown, we need to back the change out.
//
SetState(STATE_VISIBLE, value);
fireChange = true;
try {
if (value) CreateControl();
SafeNativeMethods.SetWindowPos(new HandleRef(window, Handle),
NativeMethods.NullHandleRef,
0, 0, 0, 0,
NativeMethods.SWP_NOSIZE
| NativeMethods.SWP_NOMOVE
| NativeMethods.SWP_NOZORDER
| NativeMethods.SWP_NOACTIVATE
| (value ? NativeMethods.SWP_SHOWWINDOW : NativeMethods.SWP_HIDEWINDOW));
}
catch {
SetState(STATE_VISIBLE, !value);
throw;
}
}
if (GetVisibleCore() != value) {
SetState(STATE_VISIBLE, value);
fireChange = true;
}
if (fireChange) {
// We do not do this in the OnPropertyChanged event for visible
// Lots of things could cause us to become visible, including a
// parent window. We do not want to indescriminiately layout
// due to this, but we do want to layout if the user changed
// our visibility.
//
using (new LayoutTransaction(parent, this, PropertyNames.Visible)) {
OnVisibleChanged(EventArgs.Empty);
}
}
UpdateRoot();
}
else { // value of Visible property not changed, but raw bit may have
if (!GetState(STATE_VISIBLE) && !value && IsHandleCreated) {
// PERF - setting Visible=false twice can get us into this else block
// which makes us process WM_WINDOWPOS* messages - make sure we've already
// visible=false - if not, make it so.
if (!SafeNativeMethods.IsWindowVisible(new HandleRef(this,this.Handle))) {
// we're already invisible - bail.
return;
}
}
SetState(STATE_VISIBLE, value);
// If the handle is already created, we need to update the window style.
// This situation occurs when the parent control is not currently visible,
// but the child control has already been created.
//
if (IsHandleCreated) {
SafeNativeMethods.SetWindowPos(
new HandleRef(window, Handle), NativeMethods.NullHandleRef, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE |
NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOACTIVATE |
(value ? NativeMethods.SWP_SHOWWINDOW : NativeMethods.SWP_HIDEWINDOW));
}
}
}
finally {
System.Internal.HandleCollector.ResumeCollect();
}
}
作为旁注,其中很多 "what happens under the hood" 可以通过 browsing the reference source 计算出来,或者创建一个小应用程序并反编译它(我会推荐 JetBrains DotPeek,或者 ildasm works也是)。
你没有收到"Cross-thread operation not valid"消息的原因是访问the control Handle property时抛出异常:
public IntPtr Handle {
get {
if (checkForIllegalCrossThreadCalls &&
!inCrossThreadSafeCall &&
InvokeRequired) {
throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall,
Name));
}
if (!IsHandleCreated)
{
CreateHandle();
}
return HandleInternal;
}
}
从可以看出,SetVisibleCore
(被Visible调用)多次使用Handle
,但是InstallNewImage
(被Load
调用)才不是。 (实际上,Invalidate
确实访问了句柄 属性,但它是在线程安全调用范围内访问的,这很好。)
众所周知,除了 GUI 线程之外,您不能使用任何其他线程更改 GUI。因此,一个常用的(我使用的)简单技巧是调用:
this.Invoke((MethodInvoker)delegate { pictureBox1.Visible = false; });
我正在构建我的程序并启动它,很快注意到我忘记在调用程序中放置一个 PictureBox.Load(string url)
,但是没有发生错误。
所以我很好奇,为什么不允许我这样做(在非 GUI 线程中):
pictureBox1.Visible = false; // eg.
但我可以这样做:
pictureBox1.Load(url); // url = link to image
当您加载新图像时,this is what happens 在 PictureBox
中(这是从 Load
方法中调用的):
private void InstallNewImage(Image value,
ImageInstallationType installationType)
{
StopAnimate();
this.image = value;
LayoutTransaction.DoLayoutIf(AutoSize, this, this, PropertyNames.Image);
Animate();
if (installationType != ImageInstallationType.ErrorOrInitial)
{
AdjustSize();
}
this.imageInstallationType = installationType;
Invalidate();
CommonProperties.xClearPreferredSizeCache(this);
}
所以你可以看到它真正做的就是设置图像然后调用 Invalidate
,它可以从其他线程调用。
Visible
(继承自 Control
),您可以在这里看到,它通过 p/invoke 做了很多事情并且必须在主线程 UI 上完成.
protected virtual void SetVisibleCore(bool value) {
try {
System.Internal.HandleCollector.SuspendCollect();
if (GetVisibleCore() != value) {
if (!value) {
SelectNextIfFocused();
}
bool fireChange = false;
if (GetTopLevel()) {
// The processing of WmShowWindow will set the visibility
// bit and call CreateControl()
//
if (IsHandleCreated || value) {
SafeNativeMethods.ShowWindow(new HandleRef(this, Handle), value ? ShowParams : NativeMethods.SW_HIDE);
}
}
else if (IsHandleCreated || value && parent != null && parent.Created) {
// We want to mark the control as visible so that CreateControl
// knows that we are going to be displayed... however in case
// an exception is thrown, we need to back the change out.
//
SetState(STATE_VISIBLE, value);
fireChange = true;
try {
if (value) CreateControl();
SafeNativeMethods.SetWindowPos(new HandleRef(window, Handle),
NativeMethods.NullHandleRef,
0, 0, 0, 0,
NativeMethods.SWP_NOSIZE
| NativeMethods.SWP_NOMOVE
| NativeMethods.SWP_NOZORDER
| NativeMethods.SWP_NOACTIVATE
| (value ? NativeMethods.SWP_SHOWWINDOW : NativeMethods.SWP_HIDEWINDOW));
}
catch {
SetState(STATE_VISIBLE, !value);
throw;
}
}
if (GetVisibleCore() != value) {
SetState(STATE_VISIBLE, value);
fireChange = true;
}
if (fireChange) {
// We do not do this in the OnPropertyChanged event for visible
// Lots of things could cause us to become visible, including a
// parent window. We do not want to indescriminiately layout
// due to this, but we do want to layout if the user changed
// our visibility.
//
using (new LayoutTransaction(parent, this, PropertyNames.Visible)) {
OnVisibleChanged(EventArgs.Empty);
}
}
UpdateRoot();
}
else { // value of Visible property not changed, but raw bit may have
if (!GetState(STATE_VISIBLE) && !value && IsHandleCreated) {
// PERF - setting Visible=false twice can get us into this else block
// which makes us process WM_WINDOWPOS* messages - make sure we've already
// visible=false - if not, make it so.
if (!SafeNativeMethods.IsWindowVisible(new HandleRef(this,this.Handle))) {
// we're already invisible - bail.
return;
}
}
SetState(STATE_VISIBLE, value);
// If the handle is already created, we need to update the window style.
// This situation occurs when the parent control is not currently visible,
// but the child control has already been created.
//
if (IsHandleCreated) {
SafeNativeMethods.SetWindowPos(
new HandleRef(window, Handle), NativeMethods.NullHandleRef, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE |
NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOACTIVATE |
(value ? NativeMethods.SWP_SHOWWINDOW : NativeMethods.SWP_HIDEWINDOW));
}
}
}
finally {
System.Internal.HandleCollector.ResumeCollect();
}
}
作为旁注,其中很多 "what happens under the hood" 可以通过 browsing the reference source 计算出来,或者创建一个小应用程序并反编译它(我会推荐 JetBrains DotPeek,或者 ildasm works也是)。
你没有收到"Cross-thread operation not valid"消息的原因是访问the control Handle property时抛出异常:
public IntPtr Handle {
get {
if (checkForIllegalCrossThreadCalls &&
!inCrossThreadSafeCall &&
InvokeRequired) {
throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall,
Name));
}
if (!IsHandleCreated)
{
CreateHandle();
}
return HandleInternal;
}
}
从SetVisibleCore
(被Visible调用)多次使用Handle
,但是InstallNewImage
(被Load
调用)才不是。 (实际上,Invalidate
确实访问了句柄 属性,但它是在线程安全调用范围内访问的,这很好。)