• 出现“线程无法访问非本线程创建的资源”的错误


    出现原因

    WinForm中,如果你尝试在一个线程上操作另一个线程创建的控件,就会出现“线程无法访问非本线程创建的资源”的错误。这是因为Windows窗体的设计原则是单线程模型,即只有创建该控件的线程才能对其进行操作。

    解决方法 

    1.使用 Control.InvokeControl.BeginInvoke 方法

    这是处理跨线程操作的最常见和推荐的方法。当你在一个线程中创建了一个控件,然后另一个线程想要访问这个控件时,你可以使用 Control.InvokeControl.BeginInvoke 方法。

    Control.Invoke 方法会阻塞当前线程,直到操作完成。而 Control.BeginInvoke 方法则是异步的,它会立即返回,并在后台执行操作。

    这两个方法都需要一个委托作为参数,这个委托封装了你想要执行的操作。这些操作会在创建控件的线程上执行,因此是线程安全的。

    1. public partial class Form1 : Form
    2. {
    3. public Form1()
    4. {
    5. InitializeComponent();
    6. }
    7. private void Form1_Load(object sender, EventArgs e)
    8. {
    9. Thread thread1 = new Thread(() =>
    10. {
    11. for (int i = 1; i <= 100; i += 2)
    12. {
    13. UpdateTextBox(textBox1, i.ToString());
    14. Thread.Sleep(100);
    15. }
    16. });
    17. Thread thread2 = new Thread(() =>
    18. {
    19. for (int i = 2; i <= 100; i += 2)
    20. {
    21. UpdateTextBox(textBox2, i.ToString());
    22. Thread.Sleep(100);
    23. }
    24. });
    25. thread1.Start();
    26. thread2.Start();
    27. }
    28. private void UpdateTextBox(TextBox textBox, string text)
    29. {
    30. if (textBox.InvokeRequired)
    31. {
    32. textBox.Invoke((MethodInvoker)delegate { UpdateTextBox(textBox, text); });
    33. }
    34. else
    35. {
    36. textBox.Text = text;
    37. }
    38. }
    39. }

    Control.InvokeRequired属性用于检查当前线程是否是创建控件的线程。如果当前线程是创建控件的线程,InvokeRequired返回false;如果当前线程不是创建控件的线程,InvokeRequired返回true

    因此,我们在访问控件之前先检查InvokeRequired属性,如果它返回true,那么我们就使用Control.Invoke方法来在创建控件的线程上执行操作。Control.Invoke方法接收一个委托,并在创建控件的线程上同步执行该委托。

    这样做的目的是确保我们总是在正确的线程上访问控件,避免线程冲突,保证应用程序的稳定性和正确性。

    在这个方法中,如果InvokeRequired返回true,我们就使用Control.Invoke方法来重新调用UpdateTextBox方法。这次调用将在创建textBox的线程上执行,所以InvokeRequired将返回false,然后我们就可以安全地更新textBox的Text属性。

    2.使用 BackgroundWorker 组件

    BackgroundWorker 组件提供了对后台操作的支持。你可以在 DoWork 事件处理程序中执行后台操作,然后在 ProgressChangedRunWorkerCompleted 事件处理程序中更新 UI 控件。

    因为这些事件处理程序是在创建 BackgroundWorker 的线程上引发的,所以你可以在这些事件处理程序中安全地访问 UI 控件。

    BackgroundWorker 组件还支持报告进度和取消操作,这使得它非常适合用于长时间运行的操作。

    1. using System;
    2. using System.ComponentModel;
    3. using System.Threading;
    4. using System.Windows.Forms;
    5. namespace MultiThreadedForm
    6. {
    7. public partial class Form1 : Form
    8. {
    9. private BackgroundWorker worker1;
    10. private BackgroundWorker worker2;
    11. public Form1()
    12. {
    13. InitializeComponent();
    14. InitializeBackgroundWorkers();
    15. }
    16. private void InitializeBackgroundWorkers()
    17. {
    18. // 初始化worker1
    19. worker1 = new BackgroundWorker();
    20. worker1.WorkerReportsProgress = true;
    21. worker1.DoWork += new DoWorkEventHandler(Worker1_DoWork);
    22. worker1.ProgressChanged += new ProgressChangedEventHandler(Worker1_ProgressChanged);
    23. worker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Worker1_RunWorkerCompleted);
    24. // 初始化worker2
    25. worker2 = new BackgroundWorker();
    26. worker2.WorkerReportsProgress = true;
    27. worker2.DoWork += new DoWorkEventHandler(Worker2_DoWork);
    28. worker2.ProgressChanged += new ProgressChangedEventHandler(Worker2_ProgressChanged);
    29. worker2.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Worker2_RunWorkerCompleted);
    30. }
    31. // 线程1的后台操作
    32. private void Worker1_DoWork(object sender, DoWorkEventArgs e)
    33. {
    34. for (int i = 1; i <= 100; i += 2)
    35. {
    36. worker1.ReportProgress(i);
    37. Thread.Sleep(100); // 模拟耗时操作
    38. }
    39. }
    40. // 更新textbox1的进度
    41. private void Worker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    42. {
    43. textBox1.Text = e.ProgressPercentage.ToString();
    44. }
    45. // 线程1完成后的操作
    46. private void Worker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    47. {
    48. textBox1.Text = "Thread 1 completed!";
    49. }
    50. // 线程2的后台操作
    51. private void Worker2_DoWork(object sender, DoWorkEventArgs e)
    52. {
    53. for (int i = 2; i <= 100; i += 2)
    54. {
    55. worker2.ReportProgress(i);
    56. Thread.Sleep(100); // 模拟耗时操作
    57. }
    58. }
    59. // 更新textbox2的进度
    60. private void Worker2_ProgressChanged(object sender, ProgressChangedEventArgs e)
    61. {
    62. textBox2.Text = e.ProgressPercentage.ToString();
    63. }
    64. // 线程2完成后的操作
    65. private void Worker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    66. {
    67. textBox2.Text = "Thread 2 completed!";
    68. }
    69. // 开始线程1
    70. private void StartThread1Button_Click(object sender, EventArgs e)
    71. {
    72. if (!worker1.IsBusy)
    73. {
    74. worker1.RunWorkerAsync();
    75. }
    76. }
    77. // 开始线程2
    78. private void StartThread2Button_Click(object sender, EventArgs e)
    79. {
    80. if (!worker2.IsBusy)
    81. {
    82. worker2.RunWorkerAsync();
    83. }
    84. }
    85. }
    86. }

    我们创建了两个BackgroundWorker实例worker1worker2,分别用于控制线程1和线程2的操作。当点击“开始线程1”按钮时,会启动线程1的后台操作,而点击“开始线程2”按钮时,则会启动线程2的后台操作。

    DoWork事件处理程序中,分别使用ReportProgress方法报告进度,并在ProgressChanged事件处理程序中对相应的TextBox控件进行更新。在RunWorkerCompleted事件处理程序中,对TextBox控件进行最终的更新。

    3.设置 Control.CheckForIllegalCrossThreadCalls 属性

    Control.CheckForIllegalCrossThreadCalls 是一个全局设置,会影响到所有的控件。如果你将这个属性设置为 false,那么就可以在任何线程上操作控件,而不会抛出异常。

    然而,这种方法并不推荐使用。这是因为它违反了 Windows 窗体的设计原则,可能会导致未定义的行为或者难以调试的问题。除非你完全理解这种方法的风险,并且愿意承担这些风险,否则应该避免使用这种方法。

  • 相关阅读:
    网络安全系列-四十四:使用Filebeat、ElasticSearch、Kinaba 针对Suricata的分析结果eve.json进行可视化展示
    Nacos的使用和踩过的一些坑
    LeetCode 刷题系列 -- 1425. 带限制的子序列和
    asp.net coree文件上传与下载实例
    勤于奋快速抖音推书项目
    IDEA2023.1版本新建Web项目并配置本地Tomcat
    常见问题(系统、软件、代码等故障):解决方法——总结学习
    【zabbix】企业微信告警
    ASP.NET基于WEB的选课系统
    【C++编程语言】之 加号 左移 递增 赋值 关系 函数调用 的 运算符重载
  • 原文地址:https://blog.csdn.net/weixin_54498280/article/details/134341127