jonathana jonathana - 3 months ago 18
Vb.net Question

Why it is safe to update some properties of a control from another thread?

Why it is safe to update part of the properties of

System.Windows.Forms.Control
from another thread compared to other properties that are not safe and the programmer must use a
Delegate
?

For example,
ForeColor
compare to
Text
.

Could someone explain this from a design perspective?

Answer

To answer your question as to why some property accesses raise an illegal cross-thread error and other do not, you can refer to the source code for System.Windows.Forms.Control. It takes bit of digging through it, but it does appear as though some access is suggested as as being thread-safe such as getting the Text property but setting it is not.

In reality, the conventional wisdom to treat all control access as not thread safe is the best practice.

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;
                }
            }
        }

    }
}

Note the use of the internal WindowText property in the above code.

    /// <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;
                    }
                }
            }
        }
    }

Note the use of MultithreadSafeCallScope in the get code. Also note the use of the Handle property that will throw a cross-thread error; I believe the Handle property acts as the gate-keeper that checks for cross-thread access.

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

        if (!IsHandleCreated)
        {
            CreateHandle();
        }

        return HandleInternal;
    }
}