首页 文章

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

提问于
浏览
500

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

  • 有一个主要表单托管一些用户控件 .

  • 用户控件执行一些繁重的数据操作,这样如果我直接调用 UserControl_Load 方法,则UI在加载方法执行的持续时间内无响应 .

  • 为了解决这个问题,我在不同的线程上加载数据(尝试尽可能少地更改现有代码)

  • 我使用了后台工作线程,它将加载数据,完成后将通知应用程序已完成其工作 .

  • 现在出现了一个真正的问题 . 所有UI(主窗体及其子用户控件)都是在主要主线程上创建的 . 在usercontrol的LOAD方法中,我基于userControl上的某些控件(如文本框)的值来获取数据 .

伪代码看起来像这样:

CODE 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.
    }
}

它给出的例外是

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

为了更多地了解这一点,我做了一些谷歌搜索,并提出了一个建议,如使用下面的代码

CODE 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行的影响是什么?

The situation is this :我想根据控件的值将数据加载到全局变量中 . 我不打算从子线程中做到这一点 .

因此只访问该值,以便可以从数据库中获取相应的数据 .

20 回答

  • 3

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

    两种方式:

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

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

    例:

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

    行动y; //在课堂内宣布

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

  • 379

    根据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;
            }
        }
    }
    
  • 146

    UI中的线程模型

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

    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

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

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

  • 1

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

    fsw.SynchronizingObject = this

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

  • 6

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

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

  • 6

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

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

    没有更多的麻烦 - 简单 .

  • 7

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

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

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

  • 37

    用于UI跨线程问题的最干净(和适当的)解决方案是使用SynchronizationContext,请参阅Synchronizing calls to the UI in a multi-threaded application文章,它非常好地解释了它 .

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

    使用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语句中,允许调用者告诉它在完成后返回什么类型,对调用者的异常回调:

    Adding Try Catch, Auto Exception Logging and 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
            }
        }
    
  • 6

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

  • 3

    在xamarin stuidio之外的visual studio winforms原型项目中编程iOS-Phone monotouch app控制器时,我发现需要这样做 . 我希望尽可能在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);
                }
            }
        }
    }
    
  • 11

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

    CheckForIllegalCrossThreadCalls = false
    

    Form1() 构造函数中 .

  • 3

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

    (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

    沿着与之前的答案相同的行,但是非常短的添加允许使用所有Control属性而不具有跨线程调用异常 .

    Helper Method

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

    Sample Usage

    // 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;
    }
    
  • 14
    this.Invoke(new MethodInvoker(delegate
                {
                    //your code here;
                }));
    
  • 1

    例如,从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
    
  • 4

    跨线程操作有两种选择 .

    Control.InvokeRequired Property
    

    第二个是使用

    SynchronizationContext Post Method
    

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

    Cross Thread Update UI | .Net

    Cross Thread Update UI using SynchronizationContext | .Net

相关问题