multi - c# thread update ui




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

我有一個場景。 (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行如果條件。 加載任務再次由父線程完成,而不是我產生的第三個任務。

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

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

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

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


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 。


.NET中的控件通常不是線程安全的。 這意味著您不應該從其所在線程以外的線程訪問控件。 為了解決這個問題,你需要調用控件,這是你的第二個樣本正在嘗試的內容。

但是,在你的情況下,你所做的只是將長時間運行的方法傳遞回主線程。 當然,這不是你想要做的。 您需要重新考慮這一點,以便您在主線程中執行的任何操作都是在這里和那裡設置快速屬性。


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


使用Async / Await和回調的新外觀。 如果將擴展方法保留在項目中,則只需要一行代碼。

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

您可以將其他內容添加到Extension方法中,例如將其包裝在Try / Catch語句中,從而允許調用者告知它在完成後返回什麼類型,調用方的回調異常:

添加嘗試抓取,自動例外記錄和CallBack

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

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

(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


您只需要將Invoke或BeginInvoke用於更改UI所需的最低工作量。 你的“沉重的”方法應該在另一個線程上執行(例如通過BackgroundWorker),然後使用Control.Invoke / Control.BeginInvoke來更新UI。 這樣你的UI線程將可以自由地處理UI事件等。

請參閱我的線程文章以獲取WinForms示例 - 雖然文章是在BackgroundWorker到達現場之前編寫的,但恐怕我沒有在這方面對其進行更新。 BackgroundWorker僅僅簡化了回調。


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

fsw.SynchronizingObject = this

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


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

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

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



根據Prera​​k K的更新評論 (自刪除以來):

我想我沒有正確提出這個問題。

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

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

你想要的解決方案應該如下所示:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

嘗試切換回控制線程之前 ,在單獨的線程中執行嚴肅處理。 例如:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

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

幫助者方法

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

行動y; //在類中聲明

label1.Invoke(Y =()=> label1.Text =“文本”);


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

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

this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));






invoke