跨线程操作无效:从创建它的线程以外的线程访问控制

时间:2008-09-26 21:12:42

标签: c# multithreading winforms invoke

我有一个场景。 (Windows Forms,C#,.NET)

  1. 有一个主要表单可以托管一些用户控件。
  2. 用户控件执行一些繁重的数据操作,这样如果我直接调用UserControl_Load方法,则UI在加载方法执行的持续时间内无响应。
  3. 为了解决这个问题,我在不同的线程上加载数据(尽可能少地尝试更改现有代码)
  4. 我使用了后台工作线程,它将加载数据,完成后会通知应用程序已完成其工作。
  5. 现在出现了一个真正的问题。所有UI(主窗体及其子用户控件)都是在主要主线程上创建的。在usercontrol的LOAD方法中,我基于userControl上的某些控件(如文本框)的值来获取数据。
  6. 伪代码看起来像这样:

    代码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.
        }
    }
    

    它给出的例外是

      

    跨线程操作无效:从创建它的线程以外的线程访问控件。

    要了解更多信息,我做了一些谷歌搜索,并提出了一个建议,如使用以下代码

    代码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执行。加载任务再次由父线程完成,而不是我生成的第三个。

    我不知道我是否认为这是对还是错。我是线程新手。

    如何解决此问题以及阻止执行第1行的影响是什么?

    情况就是这样:我想根据控件的值将数据加载到全局变量中。我不想从子线程更改控件的值。我不打算从子线程中做到这一点。

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

22 个答案:

答案 0 :(得分:399)

根据Prerak K's update comment(自删除后):

  

我想我没有正确提出这个问题。

     

情况是这样的:我想根据控件的值将数据加载到全局变量中。我不想从子线程更改控件的值。我不打算从子线程中做到这一点。

     

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

您想要的解决方案应该如下所示:

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

答案 1 :(得分:161)

UI中的线程模型

请阅读UI应用程序中的 Threading Model 以了解基本概念。该链接导航到描述WPF线程模型的页面。但是,Windows Forms使用相同的想法。

UI线程

  • 只有一个线程(UI线程),允许访问System.Windows.Forms.Control及其子类成员。
  • 尝试从不同于UI线程的线程访问System.Windows.Forms.Control的成员将导致跨线程异常。
  • 由于只有一个线程,所有UI操作都作为工作项排队到该线程中:

enter image description here

enter image description here

BeginInvoke和Invoke方法

enter image description here

调用

enter image description here

的BeginInvoke

enter image description here

代码解决方案

阅读问题How to update the GUI from another thread in C#?的答案。 对于C#5.0和.NET 4.5,推荐的解决方案是here

答案 2 :(得分:70)

您只想使用Invoke或BeginInvoke来完成更改UI所需的最少工作量。您的“重”方法应该在另一个线程上执行(例如,通过BackgroundWorker),然后使用Control.Invoke / Control.BeginInvoke来更新UI。这样你的UI线程就可以自由地处理UI事件等。

请参阅我的threading article获取WinForms example - 虽然文章是在BackgroundWorker到达现场之前编写的,但我担心我没有在这方面更新它。 BackgroundWorker只是稍微简化了回调。

答案 3 :(得分:40)

我遇到FileSystemWatcher的问题,发现以下代码解决了问题:

fsw.SynchronizingObject = this

然后控件使用当前表单对象来处理事件,因此将在同一个线程上。

答案 4 :(得分:25)

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

def logout(request):
    # Log out the user.
    logout(request)
    # Return to homepage.
    return HttpResponseRedirect(reverse('registeration:index'))

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

答案 5 :(得分:16)

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

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

没有更多的麻烦 - 简单。

答案 6 :(得分:16)

.NET中的控件通常不是线程安全的。这意味着您不应该从它所在的线程以外的线程访问控件。要解决这个问题,您需要调用控件,这是您的第二个示例正在尝试的控件。

但是,在您的情况下,您所做的就是将长时间运行的方法传递回主线程。当然,这不是你想要做的。你需要重新考虑这一点,这样你在主线程上所做的就是在这里和那里设置一个快速属性。

答案 7 :(得分:13)

用于UI跨线程问题的最简洁(也是正确的)解决方案是使用SynchronizationContext,请参阅Synchronizing calls to the UI in a multi-threaded application文章,它解释得非常好。

答案 8 :(得分:9)

使用Async / Await和回调的新外观。如果在项目中保留扩展方法,则只需要一行代码。

^X

您可以向Extension方法添加其他内容,例如将其包装在Try / Catch语句中,允许调用者告诉它在完成后返回什么类型,调用者的异常回调:

添加Try Catch,Auto Exception Logging和CallBack

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

答案 9 :(得分:7)

按照最简单的(在我看来)方式修改另一个线程中的对象:

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

答案 10 :(得分:7)

您需要查看Backgroundworker示例:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx 特别是它如何与UI层交互。根据您的帖子,这似乎可以解答您的问题。

答案 11 :(得分:6)

我在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);
            }
        }
    }
}

答案 12 :(得分:6)

这不是解决此错误的推荐方法,但您可以快速抑制它,它将完成这项工作。我更喜欢这个原型或演示。添加

CheckForIllegalCrossThreadCalls = false
Form1()构造函数中的

答案 13 :(得分:5)

如果您使用的对象没有

,这是另一种方法
(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,那么它是一种不同的方法

答案 14 :(得分:4)

与以前的答案一样, 但是很短的添加允许使用所有Control属性而不会有跨线程调用异常。

帮助方法

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

答案 15 :(得分:4)

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

答案 16 :(得分:3)

例如,从UI线程的Control中获取文本:

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

答案 17 :(得分:2)

Same question : how-to-update-the-gui-from-another-thread-in-c

两种方式:

  1. 在e.result中返回值并使用它在backgroundWorker_RunWorkerCompleted事件中设置yout文本框值

  2. 声明一些变量来将这些值保存在一个单独的类中(它将作为数据持有者)。创建此类的静态实例,您可以通过任何线程访问它。

  3. 示例:

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

答案 18 :(得分:0)

行动y; //在类

中声明

label1.Invoke(Y =()=&GT; label1.Text =&#34;文本&#34);

答案 19 :(得分:0)

只需使用此:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });

答案 20 :(得分:0)

解决此问题的简单且可重复使用的方法。

扩展方法

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

样品用量

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

答案 21 :(得分:-3)

跨线程操作有两种选择。

Control.InvokeRequired Property 

,第二个是使用

SynchronizationContext Post Method

Control.InvokeRequired仅在从Control类继承的工作控件时有用,而SynchronizationContext可以在任何地方使用。一些有用的信息如下链接

Cross Thread Update UI | .Net

Cross Thread Update UI using SynchronizationContext | .Net