簡體   English   中英

將線程安全的UI訪問器保存在c#中的單獨類中

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

在我的多線程應用程序中,我需要對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)

所有這一切的問題是你開始泄漏控件實際所在的表單之外的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;
    }

}

現在你可以擺脫每個控件上的所有調用垃圾。

你可以將這些東西作為擴展方法分開。 這將允許您調用對象本身中的方法,而不是像現在一樣將其作為參數傳遞。

所以你可以這樣做: label1.SetText("some text"); SetText("some text", label1); instad SetText("some text", label1);

另外一個好處是你可以為每種控件類型分別實現,因此你可以有一個用於標簽,一個用於文本框。 這將使代碼更清潔。

最后,關於使用反射來設置屬性的問題。 您可以使用Type.GetProperty()方法獲取對該屬性的引用。 這將返回一個PropertyInfo對象,您可以使用它來設置屬性值,如下所示:

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

就在你調試你的項目的時候,對吧?
無論如何,如果你有另一個選項不創建一個單獨的類來操作它,你可以在調用除自己的線程之外的線程的每個form上將此CheckForIllegalCrossThreadCalls屬性設置為false

CheckForIllegalCrossThreadCalls - MSDN

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM