跨线程操作读取属性时无效

4

当我尝试从自定义面板控件读取属性时,出现了这个错误。该属性返回面板内文本框的值。如何从另一个线程读取返回文本框控件的值的属性?以下是我的属性代码示例。我不担心setter。

以下是确切的错误信息: 跨线程操作无效:从创建它的线程以外的线程访问控件''。

public string Header
{
get
{
   return _HeaderComboBox.Text;
}
set
{
   _HeaderComboBox.Text = value;
}
}

请看这里: http://stackoverflow.com/questions/2316631/how-to-make-thread-safe-calls-to-windows-forms-controls - n535
5个回答

8

使用BeginInvoke的MSDN示例

根据您发布的getter代码片段,以下是我如何实现此示例:

public string Header {
    get {
        string text = string.Empty;
        _HeaderComboBox.BeginInvoke(new MethodInvoker(delegate {
            text = _HeaderComboBox.Text;
        }));
        return text;
    }

    set {
        _HeaderComboBox.Text = value;
    }
}

有更加优雅的方法,但这只是一个通用示例。


1
他没有说他在哪个平台上。这个链接适用于WinForms;对于WPF,请使用Dispatcher.BeginInvoke。 - itowlson
1
谢谢。我认为这解决了我的问题。唯一的问题是它一直返回一个空字符串。使用Invoke而不是BeginInvoke是正确的方法吗? - John Sheares
如果返回一个空字符串,则说明您没有设置文本属性。我在测试项目中进行了此操作以确保其有效 - 它确实返回了文本属性。 - IAbstract
1
我将它更改为Invoke,看起来可以正常工作,而BeginInvoke返回一个空字符串。我正在使用你答案中提供的完全相同的代码。 - John Sheares
使用 Invoke 替代 BeginInvoke。原始代码会在 BeginInvoke 完成之前返回值。 - qwer11121
显示剩余2条评论

3

为了访问属性,您需要将回调封送到UI线程。

在.NET 2.0之前,您必须调用Control类上的Invoke方法,以便将调用封送到Text属性。

在.NET 2.0及更高版本中,如果您的后台线程可以访问UI线程的SynchronizationContext,则可以调用Send方法将回调封送回UI。

请注意,如果您不需要等待调用的结果(就像在这里一样,因为您想要调用Text属性的结果),则可以分别在Control和SynchronizationContext上调用BeginInvoke和Post。但是,在此之前,请注意。BeginInvokePost

2

除了创建它的UI线程外,您无法在任何其他线程上访问WinForms控件,因为存在跨线程问题、竞争条件等。要解决此问题,您必须在UI线程上运行想要运行的任何命令。这可以通过使用Invoke方法来完成:

public void InvokeExample()
{
    if (InvokeRequired)
    {
        // Invoke this method on the UI thread using an anonymous delegate
        Invoke(new MethodInvoker(() => InvokeExample()));
        return;
    }

    string header = Control.Header;
}

0

你需要将跨线程的代码外包到一个单独的方法中,创建一个委托并在你想要更改的线程上通过Invoke来调用它。你也可以使用闭包代替委托+方法。


0

我在类似的帖子中找到了答案: 如何从另一个线程更新GUI?

我扩展了它,用这个自定义类来扩展System.Windows.Forms.Control对象,其中包含SetPropertyThreadSafeGetPropertyThreadSafe。 要使用这个扩展,你只需要像这样包含命名空间:

using ControlExtention;

以下是我的代码:

using System;
using System.Linq.Expressions;
using System.Windows.Forms;

namespace ControlExtention
{
    public static class ControlExtentions
    {
        // https://dev59.com/wnRB5IYBdhLWcg3wUVtd
        // Usage: myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
        private delegate void SetPropertyThreadSafeDelegate<TResult>(Control @this, Expression<Func<TResult>> property, TResult value);

        /// <summary>
        /// Set property from a Thread other than the UI Thread safely.
        /// Use with Lambda-Expression: ControlObject.SetPropertyThreadSafe(() => ControlObject.Property, NewPropertyValue);
        /// </summary>
        /// <typeparam name="TResult">Do not set.</typeparam>
        /// <param name="this">Use lambda expression.</param>
        /// <param name="property">Use lambda expression.</param>
        /// <param name="value">Use lambda expression.</param>
        public static void SetPropertyThreadSafe<TResult>(this Control @this, Expression<Func<TResult>> property, TResult value)
        {
            System.Reflection.PropertyInfo propertyInfo = (property.Body as MemberExpression).Member as System.Reflection.PropertyInfo;

            // check ob property überhaupt ein teil von this ist
            if (propertyInfo == null || !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) || @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
            {
                throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
            }

            if (@this.InvokeRequired)
            {
                @this.Invoke(new SetPropertyThreadSafeDelegate<TResult>(SetPropertyThreadSafe), new object[] { @this, property, value });
            }
            else
            {
                @this.GetType().InvokeMember(propertyInfo.Name, System.Reflection.BindingFlags.SetProperty, null, @this, new object[] { value });
            }
        }

        private delegate TResult GetPropertyThreadSafeDelegate<TResult>(Control @this, Expression<Func<TResult>> property);

        /// <summary>
        /// Get property from a Thread other than the UI Thread safely.
        /// Use with Lambda-Expression: value = ControlObject.GetPropertyThreadSafe(() => ControlObject.Property);
        /// </summary>
        /// <typeparam name="TResult">Do not set.</typeparam>
        /// <param name="this">Use lambda expression.</param>
        /// <param name="property">Use lambda expression.</param>
        /// <param name="value">Use lambda expression.</param>
        public static TResult GetPropertyThreadSafe<TResult>(this Control @this, Expression<Func<TResult>> property)
        {
            System.Reflection.PropertyInfo propertyInfo = (property.Body as MemberExpression).Member as System.Reflection.PropertyInfo;

            // check ob property überhaupt ein teil von this ist
            if (propertyInfo == null || !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) || @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
            {
                throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
            }

            if (@this.InvokeRequired)
            {
                return (TResult)@this.Invoke(new GetPropertyThreadSafeDelegate<TResult>(GetPropertyThreadSafe), new object[] { @this, property });
            }
            else
            {
                return (TResult)@this.GetType().InvokeMember(propertyInfo.Name, System.Reflection.BindingFlags.GetProperty, null, @this, new object[] { });
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接