c# - 跨线程操作无效:从其创建线程以外的线程访问控制




multithreading winforms (14)

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 。

我有一个场景。 (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行如果条件。 加载任务再次由父线程完成,而不是我产生的第三个任务。

我不知道我是否察觉到这个对或错。 我是线程新手。

我该如何解决这个问题,以及第一行如果执行块的执行效果如何?

情况是这样的 :我想根据控件的值将数据加载到全局变量中。 我不想从子线程更改控件的值。 我不会从一个子线程完成它。

所以只能访问该值,以便可以从数据库中获取相应的数据。


.NET中的控件通常不是线程安全的。 这意味着您不应该从其所在线程以外的线程访问控件。 为了解决这个问题,你需要调用控件,这是你的第二个样本正在尝试的内容。

但是,在你的情况下,你所做的只是将长时间运行的方法传递回主线程。 当然,这不是你想要做的。 您需要重新考虑这一点,以便您在主线程中执行的任何操作都是在这里和那里设置快速属性。


与前面的答案一样,但是有一个很短的添加,它允许使用所有控件属性而不会出现跨线程调用异常。

帮助者方法

/// <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层进行交互。 根据你的发布,这似乎回答你的问题。


例如,要从UI线程的控件中获取文本:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
         text = CStr(ctl.Invoke(New GetControlTextInvoker(AddressOf GetControlText), _
                           ctl))
    Else
        text = ctl.Text
    End If

Return text
End Function

如果您正在使用的对象没有,则可以使用另一种方法

(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


我发现需要在与表单相关的所有方法中散布的检查和调用代码太冗长和不必要。 下面是一个简单的扩展方法,可以让你完全消除它:

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");

没有更多的混乱 - 简单。


我在xamarin stuidio之外的Visual Studio winforms原型项目中编写iOS-Phone monotouch应用程序控制器时发现需要此操作。 尽可能使用VS在xamarin工作室编程,我希望控制器能够完全脱离手机框架。 通过这种方式为Android和Windows Phone等其他框架实现这一点对于将来的使用会更容易。

我想要一个解决方案,让GUI可以响应事件,而无需处理每次单击按钮后面的跨线程切换代码。 基本上让类控制器处理,以保持客户端代码的简单。 你可能会在GUI上发生很多事件,就好像你可以在班级中的一个地方处理它一样会更清洁。 我不是一个多领域的专家,让我知道这是否有缺陷。

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

GUI窗体不知道控制器正在运行异步任务。

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

我现在知道它太晚了。 但是,即使在今天,如果您在访问交叉线程控件时遇到困难, 这是迄今为止最短的答案:P

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

这是我从一个线程访问任何表单控件的方式。




行动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