[C#] 跨線程操作無效:從其創建線程以外的線程訪問控制


Answers

UI中的線程模型

請閱讀UI應用程序中的線程模型 ,以了解基本概念。 鏈接導航到描述WPF線程模型的頁面。 但是,Windows Forms使用相同的想法。

UI線程

  • 只有一個線程(UI線程),允許訪問System.Windows.Forms.Control及其子類成員。
  • 嘗試從不同於UI線程的線程訪問System.Windows.Forms.Control成員將導致跨線程異常。
  • 由於只有一個線程,因此所有UI操作都作為工作項排入該線程:

BeginInvoke和Invoke方法

調用

的BeginInvoke

代碼解決方案

在問題中閱讀答案如何從C#中的其他線程更新GUI? 。 對於C#5.0和.NET 4.5,推薦的解決方案在here 。

Question

我有一個場景。 (Windows窗體,C#,.NET)

  1. 有一個主窗體託管一些用戶控件。
  2. 用戶控件執行一些繁重的數據操作,因此如果我直接調用UserControl_Load方法,則UI在執行加載方法期間無響應。
  3. 為了克服這個問題,我在不同的線程上加載數據(試圖盡可能少地改變現有的代碼)
  4. 我使用了一個後台工作線程,它將加載數據,完成後會通知應用程序它已完成工作。
  5. 現在有一個真正的問題。 所有UI(主窗體及其子控件)都是在主主線程上創建的。 在usercontrol的LOAD方法中,我基於userControl上的某些控件(如文本框)的值獲取數據。

偽代碼看起來像這樣:

代碼1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

它給出的例外是

跨線程操作無效:從其創建線程以外的線程訪問控制。

為了更多地了解這一點,我做了一些Google搜索,並提出了使用下面的代碼的建議

代碼2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

但是但是......看來我已經回到原點了。 該應用程序再次變得無響應。 這似乎是由於執行第1行如果條件。 加載任務再次由父線程完成,而不是我產生的第三個任務。

我不知道我是否察覺到這個對或錯。 我是線程新手。

我該如何解決這個問題,以及第一行如果執行塊的執行效果如何?

情況是這樣的 :我想根據控件的值將數據加載到全局變量中。 我不想從子線程更改控件的值。 我不會從一個子線程完成它。

所以只能訪問該值,以便可以從數據庫中獲取相應的數據。







如果您正在使用的對像沒有,則可以使用另一種方​​法

(InvokeRequired)

如果您正在使用除主窗體以外的類中的主窗體並且主窗體中沒有InvokeRequired對象

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

它和上面的一樣,但是如果你沒有一個帶有invokerequired的對象,但是它有訪問MainForm




遵循最簡單的(以我的觀點)來修改另一個線程中的對象:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}



我發現需要在與表單相關的所有方法中散佈的檢查和調用代碼太冗長和不必要。 下面是一個簡單的擴展方法,可以讓你完全消除它:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

然後你可以簡單地做到這一點:

textbox1.Invoke(t => t.Text = "A");

沒有更多的混亂 - 簡單。




我有FileSystemWatcher這個問題,並發現下面的代碼解決了這個問題:

fsw.SynchronizingObject = this

該控件然後使用當前表單對象來處理事件,並因此將在同一個線程上。




我現在知道它太晚了。 但是,即使在今天,如果您在訪問交叉線程控件時遇到困難, 這是迄今為止最短的答案:P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

這是我從一個線程訪問任何表單控件的方式。




同樣的問題:如何更新-gui-from-another-thread-in-c

兩種方式:

  1. 返回e.result中的值並使用它在backgroundWorker_RunWorkerCompleted事件中設置文本框值

  2. 聲明一些變量來將這些類型的值保存在一個單獨的類中(它將作為數據持有者)。 創建這個類的靜態實例,你可以通過任何線程訪問它。

例:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}



與前面的答案一樣,但是有一個很短的添加,它允許使用所有控件屬性而不會出現跨線程調用異常。

幫助者方法

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

樣例用法

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}



你需要看看Backgroundworker的例子:
BackgroundWorker特別是它如何與UI層進行交互。 根據你的發布,這似乎回答你的問題。