【问题标题】:Why am I allowed to load an image in the non-GUI thread?为什么允许我在非 GUI 线程中加载图像?
【发布时间】:2016-01-18 06:18:59
【问题描述】:

众所周知,您不能使用除 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.Invoke,你实际上是在GUI线程中调用,而不是在非GUI线程中。
  • 哎呀,措辞错误。已编辑,谢谢!

标签: c# multithreading winforms invoke


【解决方案1】:

当您加载新图像时,PictureBox 内的 this is what happens(这是从 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();
        }
    }

顺便说一句,browsing the reference source 可以解决很多这些“幕后发生的事情”,或者创建一个小型应用程序并对其进行反编译(我会推荐 JetBrains DotPeek,或者 ildasm 也可以) .

【讨论】:

  • 如果反对者不介意发表评论,我想知道问题出在哪里。
【解决方案2】:

没有收到“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;
    }
}

the accepted answer 可以看出,SetVisibleCore(由 Visible 调用)多次使用Handle,但InstallNewImage(由Load 使用)没有。 (实际上,Invalidate 确实访问了 Handle 属性,但它是在线程安全的调用范围内访问的,这很好。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-05
    • 2019-04-04
    • 2014-12-14
    • 2016-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多