将线程安全的UI访问器保存在c#中的单独类中

时间:2011-05-23 08:27:32

标签: c# winforms multithreading

在我的多线程应用程序中,我需要对UI元素进行跨线程访问,我使用线程安全方法来执行此操作。我在我的许多项目中反复使用它,并将它们保存在表单文件本身使文件看起来很难看。所以我想创建一个单独的类,我可以把所有这些,并在需要时调用它们,但我遇到了麻烦。对于更改控件的文本元素的instace,我正在使用以下

delegate void SetTextCallback(string text, Control ctrl);

public void SetText(string text, Control ctrl)
    {
        if (ctrl.InvokeRequired)
        {
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text, ctrl });
        }
        else
        {
            if (ctrl.GetType() == typeof(Label))
            {
                ctrl.Text = text;
            }
            else
            {
                ctrl.Text += Environment.NewLine + text;
            }
        }
    }

并将此功能称为

SetText("some text",label1);

如果它在表单类中,这可以正常工作,如果我把它放到另一个类中我在行中出错

this.Invoke(d, new object[] { text, ctrl });

有人可以告诉我如何才能正确地做到这一点。

也有可能有一个UI访问器方法做所有的东西,现在我有多个方法,如这一个更改文本一个更改启用属性一个更改背面颜色和一个更改前面的颜色。有可能用

之类的东西来做
public void ChangePropert(Control ctrl,Property prop,Value val)

3 个答案:

答案 0 :(得分:2)

所有这一切的问题是你开始在控件实际所在的表单之外泄漏UI代码。一个线程不应该知道控件,它应该工作并更新主线程,让主线程担心在UI中需要做什么。

实现此目的的方法是有一个第二个线程可以调用的回调,但强制该回调实际上在主线程上执行而不是在第二个线程上执行。您可以使用“同步”上下文来完成此操作。

您需要将辅助线程包装在一个可以保持对主线程同步上下文的引用的类中。然后辅助线程可以使用它进行回叫。

示例:

public partial class Form1 : Form
{
    private SynchronizationContext _synchronizationContext;

    public Form1()
    {
        InitializeComponent();
        //Client must be careful to create sync context somehwere they are sure to be on main thread
        _synchronizationContext = AsyncOperationManager.SynchronizationContext;
    }

    //Callback method implementation - must be of this form
    public void ReceiveThreadData(object threadData)
    {
        // This callback now exeutes on the main thread.
        // Can use directly in UI without error
        this.listBoxMain.Items.Add((string)threadData);
    }

    private void DoSomeThreadWork()
    {
        // Thread needs callback and sync context so it must be wrapped in a class.
        SendOrPostCallback callback = new SendOrPostCallback(ReceiveThreadData);
        SomeThreadTask task = new SomeThreadTask(_synchronizationContext, callback);
        Thread thread = new Thread(task.ExecuteThreadTask);
        thread.Start();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        DoSomeThreadWork();
    }

}

你的线程类看起来像这样:

/// SomeThreadTask defines the work a thread needs to do and also provides any data ///required along with callback pointers etc.
/// Populate a new SomeThreadTask instance with a synch context and callnbackl along with ///any data the thread needs
/// then start the thread to execute the task.
/// </summary>
public class SomeThreadTask
{

    private string _taskId;
    private SendOrPostCallback _completedCallback;
    private SynchronizationContext _synchronizationContext;

    /// <summary>
    /// Get instance of a delegate used to notify the main thread when done.
    /// </summary>
    internal SendOrPostCallback CompletedCallback
    {
        get { return _completedCallback; }
    }

    /// <summary>
    /// Get SynchronizationContext for main thread.
    /// </summary>
    internal SynchronizationContext SynchronizationContext
    {
        get { return _synchronizationContext; }
    }

    /// <summary>
    /// Thread entry point function.
    /// </summary>
    public void ExecuteThreadTask()
    {

        //Just sleep instead of doing any real work
        Thread.Sleep(5000);

        string message = "This is some spoof data from thread work.";

        // Execute callback on synch context to tell main thread this task is done.
        SynchronizationContext.Post(CompletedCallback, (object)message);


    }

    public SomeThreadTask(SynchronizationContext synchronizationContext, SendOrPostCallback callback)
    {
        _synchronizationContext = synchronizationContext;
        _completedCallback = callback;
    }

}

现在你可以摆脱每个控件上的所有调用垃圾。

答案 1 :(得分:0)

你可以将这些东西分开作为扩展方法。这将允许您调用对象本身中的方法,而不是像现在一样将其作为参数传递。

所以你可以这样做:label1.SetText("some text"); SetText("some text", label1);

的instad

另外一个好处是你可以为每种控件类型分别实现,所以你可以有一个用于标签,一个用于文本框。这将使代码更清洁。

最后,关于使用反射来设置属性的问题。您可以使用Type.GetProperty()方法获取对该属性的引用。这将返回一个PropertyInfo对象,您可以使用它来设置属性值,如下所示:

var textProperty = label1.GetType().GetProperty("Text");
textProperty.SetValue(label1, "some text", null);

答案 2 :(得分:-1)

正是在你调试你的项目时,对吧? 无论如何,如果你有另一个选项不创建一个单独的类来操作它,你可以在调用除自己的线程以外的线程的每个CheckForIllegalCrossThreadCalls上将此false属性设置为form。 / p>

CheckForIllegalCrossThreadCalls - MSDN