zoukankan      html  css  js  c++  java
  • C# 多线程详解 Part.02(UI 线程和子线程的互动、ProgressBar 的异步调用)

           我们先来看一段运行时会抛出 InvalidOperationException 异常的代码段:

    private void btnThreadA_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(ChangeTextBox);
        thread.IsBackground = true;
        thread.Start();
    }
     
    void ChangeTextBox()
    {
        for (int i = 0; i < 10000; i++)
        {
            int num = Int32.Parse(txtNum.Text);
            num++;
            txtNum.Text = num.ToString();
        }
    }

    image 

           微软在子线程修改 UI 线程的控件值时给出的安全限制方案为:在 VS2005 或者更高版本中,只要不是在控件的创建线程(一般就是指UI主线程)上访问控件的属性就会抛出这个错误,解决方法就是利用控件提供的 Invoke 和 BeginInvoke 把调用封送回 UI 线程,也就是让控件属性修改在UI线程上执行;或者 禁用此安全限制。

           解决方案一:解除该控件上对错误线程调用的检查(谨慎使用)。

    public Form2()
    {
        InitializeComponent();
     
        // 解除 TextBox 对错误线程调用的检查
        // 如果要捕获了对错误线程的调用,则为 true(默认值);否则为 false
        // 对控件权限可以开放的更大 例如 Control、Form 等
        TextBox.CheckForIllegalCrossThreadCalls = false; 
    }

           解决方案二:

    void ChangeTextBox(string str)
    {
        txtNum.Text = str;
    }
     
    // 增加一个委托
    delegate void ChangeTextBoxEventHandler(string str);
     
    // 次循环必须在子线程上运行,然后将最新值传递到 UI 线程 文本框才会即时变化
    // 如果循环写在 ChangeTextBox 函数中,那么循环真实运行权会交由 UI 线程,你只会直接看见结果,看不到过程
    void ThreadRun1()
    {
        for (int i = 0; i < 10000; i++)
        {
            int num = int.Parse(txtNum.Text);
            num++;
            
            // 使用 Invoke 方法,将函数运行劝交回给 UI 线程
            this.Invoke(new ChangeTextBoxEventHandler(ChangeTextBox), num.ToString());
        }
    }
     
    private void btnThreadB_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(ThreadRun1);
        thread.IsBackground = true;
        thread.Start();
    }

           这样已经能够达到效果,但不是微软案例推荐的写法。考虑到 ChangeTextBox 方法除了被子线程调用外,也可能被程序其它部分调用。因此,再次修改代码如下:

    void ChangeTextBox(string str)
    {
        // InvokeRequired 值判断当前修改文本框的请求是否有必要交由 UI 线程来完成
        // 如果为 Ture,说明次访问控件的行为来自子线程,则调用 Invoke 方法将代码执行权交给 UI 线程
        // 注意,下面实质上是进行了一次方法回调自身的行为,区别在于再次调用自身时,已经是 UI 线程在执行了
        if (this.InvokeRequired)
        {
            this.Invoke(new ChangeTextBoxEventHandler(ChangeTextBox), str);
        }
        else
        {
            txtNum.Text = str;
        }   
    }
     
    // 增加一个委托
    delegate void ChangeTextBoxEventHandler(string str);
     
    // 次循环必须在子线程上运行,然后将最新值传递到 UI 线程 文本框才会即时变化
    // 如果循环写在 ChangeTextBox 函数中,那么循环真实运行权会交由 UI 线程,你只会直接看见结果,看不到过程
    void ThreadRun1()
    {
        for (int i = 0; i < 10000; i++)
        {
            int num = int.Parse(txtNum.Text);
            num++;
            ChangeTextBox(num.ToString());
        }
    }
     
    private void btnThreadB_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(ThreadRun1);
        thread.IsBackground = true;
        thread.Start();
    }

           与 Invoke 方法相对应的还有 BeginInvoke ()、EndInvoke () 这些异步方法。无论是同步还是异步,这些方法总是会通过代理重新回到 UI 线程上执行。

           这些方法向 UI 线程的消息队列中放入一个消息,当 UI 线程处理这个消息时,就会在自己的上下文中执行传入的方法。换句话说,凡是使用 BeginInvoke 和 Invoke 调用的线程都是在UI主线程中执行的,所以即使这些方法里涉及到一些静态变量,也不用考虑加锁的问题。

     

     

    ProgressBar 的异步调用

           在我们应用程序开发过程中,经常会遇到一些问题,需要使用多线程技术来加以解决。

           许多种类的应用程序都需要长时间操作,比如:执行一个打印任务,请求一个 Web Service 调用等。用户在这种情况下一般会去转移做其他事情来等待任务的完成,同时还希望随时可以监控任务的执行进度。

           为什么在我们切换应用程序后,会发生屏幕假死的现象呢?

           这是因为当你切换当前应用程序到后台再切换回前台时,系统需要在屏幕上重画整个用户界面。但是应用程序正在执行长任务,根本没有时间处理用户界面的重画,问题就会发生。如何解决问题呢?我们需要将长任务放在后台运行,把用户界面线程解放出来,因此我们需要另外一个线程。

          

           如何避免多线程的窗体资源访问的安全问题呢?其实非常简单,有两种方法:

    1. 不管线程是否是用户界面线程,对用户界面资源的访问统一由委托完成!
    2. 在每个 Windows Forms 用户界面类中都有一个 InvokeRequired 属性,它用来标识当前线程是否是来自UI线程之外的线程。检查这个属性的值可以决定是否需要进行异步调用委托。

     

    情况一:

    delegate void ShowProgressDelegate(int totalStep, int currentStep);
    delegate void RunTaskDelegate(int seconds);
     
    void ShowProgress(int totalStep, int currentStep)
    {
        progressBar1.Maximum = totalStep;
        progressBar1.Value = currentStep;
    }
     
    void RunTask(int seconds)
    {
        ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);
     
        // 每 1/4 秒显示一次进度
        for (int i = 0; i < seconds * 4; i++)
        {
            Thread.Sleep(250);
            this.Invoke(showProgress, new object[] { seconds * 4, i + 1 });
        }
    }
     
    private void button1_Click(object sender, EventArgs e)
    {
        RunTaskDelegate runTask = new RunTaskDelegate(RunTask);
     
        // 委托异步调用方式
        runTask.BeginInvoke(Convert.ToInt32(this.textBox1.Text), null, null);
    }

    image

     

    情况二:

    delegate void ShowProgressDelegate(int totalStep, int currentStep);
    delegate void RunTaskDelegate(int seconds);
     
    void ShowProgress(int totalStep, int currentStep)
    {
        if (progressBar1.InvokeRequired)
        {
            ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);
            this.BeginInvoke(showProgress, new object[] { totalStep, currentStep });
        }
        else
        {
            progressBar1.Maximum = totalStep;
            progressBar1.Value = currentStep;
        }
    }
     
    void RunTask(int seconds)
    {
        // 每 1/4 秒显示一次进度
        for (int i = 0; i < seconds * 4; i++)
        {
            Thread.Sleep(250);
            ShowProgress(seconds * 4, i + 1);
        }
    }
     
    private void button1_Click(object sender, EventArgs e)
    {
        RunTaskDelegate runTask = new RunTaskDelegate(RunTask);
     
        // 委托异步调用方式
        runTask.BeginInvoke(Convert.ToInt32(this.textBox1.Text), null, null);
    }
  • 相关阅读:
    SAP ABAP 开发中常用的方法总结
    安装sap帮助文档
    spring成神之路第七篇:DI依赖注入之手动注入
    spring成神之路第三篇:Spring容器基本使用及原理(ApplicationContext接口获取bean的4种方式)
    spring成神之路第九篇:dependon到底是干什么的?
    spring成神之路第十篇:primary可以解决什么问题?
    spring成神之路第四篇:xml中bean定义详解
    spring成神之路第六篇:玩转bean scope,避免跳坑里
    spring成神之路第一篇:为何要学spring
    spring成神之路第五篇:创建bean实例的方式
  • 原文地址:https://www.cnblogs.com/SkySoot/p/3494399.html
Copyright © 2011-2022 走看看