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

发布于 2024-11-08 18:19:23 字数 1146 浏览 0 评论 0原文

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

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)

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. 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

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. Is it possible to do it with something like

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

岁月打碎记忆 2024-11-15 18:19:23

所有这一切的问题是您开始将 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;
    }

}

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

The problem with all this is you are starting to leak UI code outside of the form where the controls actually reside. 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.

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.

简单气质女生网名 2024-11-15 18:19:23

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

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

另一个好处是你可以每种控件类型都有单独的实现,因此您可以使用一个用于标签,一个用于文本框。这将使代码更加清晰。

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

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

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"); instad of 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. This returns a PropertyInfo object that you can use to set the property value like this:

var textProperty = label1.GetType().GetProperty("Text");
textProperty.SetValue(label1, "some text", null);
粉红×色少女 2024-11-15 18:19:23

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

CheckForIllegalCrossThreadCalls - MSDN

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.

CheckForIllegalCrossThreadCalls - MSDN

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文