简体   繁体   English

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

[英]keeping the thread safe UI accessors in a separate class in c#

In my multi threaded apps i need to do cross thread access on UI elements and i am using the thread safe methods to do that. 在我的多线程应用程序中,我需要对UI元素进行跨线程访问,我使用线程安全方法来做到这一点。 I am repeatedly using this a lot in many of my projects and keeping them in the form file itself is making the file look ugly. 我在我的许多项目中反复使用它,并将它们保存在表单文件本身使文件看起来很难看。 So i want to create a seprate class where i can put all this and call them whenever needed but i am having trouble with it. 所以我想创建一个单独的类,我可以把所有这些,并在需要时调用它们,但我遇到了麻烦。 For instace for changing the text element of a control i am using the following 对于更改控件的文本元素的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;
            }
        }
    }

and call this function as 并将此函数称为

SetText("some text",label1);

This works fine if it is in the form class, if i put it into another class i am getting an error in the line 如果它在表单类中,这可以正常工作,如果我把它放到另一个类中我在行中出错

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

Can some one tell me how can i do this properly. 有人可以告诉我,我该怎样才能做到这一点。

Also is it possible to have one UI accessor method do all the stuff, that is right now i am having multiple methods like this one to change the text one to change the enabled property one to change the back color and one to change the fore color. 也有可能有一个UI访问器方法做所有的东西,现在我有多个方法,如这一个更改文本一个更改启用属性一个更改背面颜色和一个更改前面的颜色。 Is it possible to do it with something like 是否可以用类似的东西来做

public void ChangePropert(Control ctrl,Property prop,Value val)

The problem with all this is you are starting to leak UI code outside of the form where the controls actually reside. 所有这一切的问题是你开始泄漏控件实际所在的表单之外的UI代码。 A thread should not have to know about controls, it should do work and update the main thread and let the main thread worry about what needs to be done in the UI. 一个线程不应该知道控件,它应该工作并更新主线程,让主线程担心在UI中需要做什么。

The way to accomplish this is have a callback that a second thread can call, but force that callback to actually be executed on the main thread instead of executed on the second thread. 实现此目的的方法是有一个第二个线程可以调用的回调,但强制该回调实际上在主线程上执行而不是在第二个线程上执行。 You can accomplish this by using the Synchronization context. 您可以使用“同步”上下文来完成此操作。

You need to wrap your secondary threads in a class that can keep a reference to the main thread synchronization context. 您需要将辅助线程包装在一个可以保持对主线程同步上下文的引用的类中。 Then the secondary threads can use this for call backs. 然后辅助线程可以使用它进行回叫。

Example: 例:

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

}

And your thread class will look something like this: 你的线程类看起来像这样:

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

}

Now you can just get rid of all the invoke crap on every control. 现在你可以摆脱每个控件上的所有调用垃圾。

You could separate this stuff out as Extension methods. 你可以将这些东西作为扩展方法分开。 That would allow you to call methods in the object itself instead of passing it in as a parameter like you do now. 这将允许您调用对象本身中的方法,而不是像现在一样将其作为参数传递。

So you could do: label1.SetText("some text"); 所以你可以这样做: label1.SetText("some text"); instad of SetText("some text", label1); SetText("some text", label1); instad SetText("some text", label1);

An additional gain would be that you could have separate implementations for each control type, so you could have one for label and one for the text box. 另外一个好处是你可以为每种控件类型分别实现,因此你可以有一个用于标签,一个用于文本框。 This would make the code somewhat cleaner. 这将使代码更清洁。

Finally, regarding your question about using reflection to set the properties. 最后,关于使用反射来设置属性的问题。 You can get a reference to the property using the Type.GetProperty() method. 您可以使用Type.GetProperty()方法获取对该属性的引用。 This returns a PropertyInfo object that you can use to set the property value like this: 这将返回一个PropertyInfo对象,您可以使用它来设置属性值,如下所示:

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

It is just while you debugging your project, right? 就在你调试你的项目的时候,对吧?
anyway, if you had another option not to create a separate class to manipulate this, you can set this CheckForIllegalCrossThreadCalls property to false on each form that calls threads other than its own thread. 无论如何,如果你有另一个选项不创建一个单独的类来操作它,你可以在调用除自己的线程之外的线程的每个form上将此CheckForIllegalCrossThreadCalls属性设置为false

CheckForIllegalCrossThreadCalls - MSDN CheckForIllegalCrossThreadCalls - MSDN

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM