为什么从另一个线程更新控件的某些属性是安全的?

时间:2016-08-08 21:56:50

标签: c# .net vb.net

为什么从其他线程更新System.Windows.Forms.Control的部分属性与不安全且程序员必须使用Delegate的其他属性相比是否安全?

例如,ForeColorText进行比较。

有人可以从设计角度解释这个吗?

1 个答案:

答案 0 :(得分:4)

要回答您的问题,为什么某些属性访问会引发非法的跨线程错误而其他属性不会引发错误,您可以参考source code for System.Windows.Forms.Control。它需要深入挖掘它,但看起来似乎有些访问被建议为线程安全,例如获取Text属性但设置它不是。

实际上,将所有控制访问视为非线程安全的传统智慧是最佳实践。

public virtual string Text {
    get {
        if (CacheTextInternal) {
            return(text == null) ? "" : text;
        }
        else {
            return WindowText;
        }
    }

    set {
        if (value == null) {
            value = "";
        }

        if (value == Text) {
            return;
        }

        if (CacheTextInternal) {
            text = value;
        }
        WindowText = value;
        OnTextChanged(EventArgs.Empty);

        if( this.IsMnemonicsListenerAxSourced ){
            for( Control ctl = this; ctl != null; ctl = ctl.ParentInternal ) {
                ActiveXImpl activeXImpl = (ActiveXImpl)ctl.Properties.GetObject(PropActiveXImpl);
                if( activeXImpl != null ) {
                    activeXImpl.UpdateAccelTable();
                    break;
                }
            }
        }

    }
}

请注意在上面的代码中使用内部WindowText属性。

    /// <devdoc>
    ///     The current text of the Window; if the window has not yet been created, stores it in the control.
    ///     If the window has been created, stores the text in the underlying win32 control.
    ///     This property should be used whenever you want to get at the win32 control's text. For all other cases,
    ///     use the Text property - but note that this is overridable, and any of your code that uses it will use
    ///     the overridden version in controls that subclass your own.
    /// </devdoc>
    internal virtual string WindowText {
        get {

            if (!IsHandleCreated) {
                if (text == null) {
                    return "";
                }
                else {
                    return text;
                }
            }

            using (new MultithreadSafeCallScope()) {

                // it's okay to call GetWindowText cross-thread.
                //

                int textLen = SafeNativeMethods.GetWindowTextLength(new HandleRef(window, Handle));

                // Check to see if the system supports DBCS character
                // if so, double the length of the buffer.
                if (SystemInformation.DbcsEnabled) {
                    textLen = (textLen * 2) + 1;
                }
                StringBuilder sb = new StringBuilder(textLen + 1);
                UnsafeNativeMethods.GetWindowText(new HandleRef(window, Handle), sb, sb.Capacity);
                return sb.ToString();
            }
        }
        set {
            if (value == null) value = "";
            if (!WindowText.Equals(value)) {
                if (IsHandleCreated) {
                    UnsafeNativeMethods.SetWindowText(new HandleRef(window, Handle), value);
                }
                else {
                    if (value.Length == 0) {
                        text = null;
                    }
                    else {
                        text = value;
                    }
                }
            }
        }
    }

请注意MultithreadSafeCallScope代码中get的使用。还要注意使用会引发跨线程错误的Handle属性;我相信Handle属性充当 gate-keeper ,用于检查跨线程访问。

public IntPtr Handle {
    get {
        if (checkForIllegalCrossThreadCalls &&
            !inCrossThreadSafeCall &&
            InvokeRequired) {
            throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall,
                                                             Name));
        }

        if (!IsHandleCreated)
        {
            CreateHandle();
        }

        return HandleInternal;
    }
}