C#

多线程

Posted by LCY on May 7, 2019

同步编程与异步编程

通常情况下,我们写的C#代码就是同步的,运行在同一个线程中,从程序的第一行代码到最后一句代码顺序执行。而异步编程的核心是使用多线程,通过让不同的线程执行不同的任务,实现不同代码的并行运行

使用委托实现异步操作,BeginInvoke\BeginInvoke使用示数

private void btnStart_Click(object sender, EventArgs e)
{
    int baseNum = default(int);

    if (!int.TryParse(txtBaseNum.Text, out baseNum))
    {
        MessageBox.Show("请输入一个整数");
        return;
    }
    txtResult.Clear();
    //用于报告进度的对象
    IProgress<int> progressReporter = new Progress<int>((p) => this.progressBar1.Value = p);

    //计算阶乘的委托实例
    Func<int, BigInteger> ComputeAction = (bsNum) =>
    {
        BigInteger bi = new BigInteger(1);
        for (int i = 1; i <= bsNum; i++)
        {
            bi *= i;// 相乘
            //计算当前进度
            double ps = Convert.ToDouble(i) / Convert.ToDouble(bsNum) * 100d;
            progressReporter.Report(Convert.ToInt32(ps));
        }
        return bi;
    };
    //开始调用
    btnStart.Enabled = false;
    ComputeAction.BeginInvoke(baseNum, new AsyncCallback(Finishcallback), ComputeAction);
}

private void Finishcallback(IAsyncResult ar)
{
    //取出委托变量
    Func<int, BigInteger> action = (Func<int, BigInteger>)ar.AsyncState;
    //得到计算结果
    BigInteger res = action.EndInvoke(ar);
    this.BeginInvoke(new Action(() =>
    {
        btnStart.Enabled = true;
        //显示计算结果
        txtResult.Text = string.Format("计算结果:{0}", res);
    }
    ));
}

前台线程与后台线程

关于多线程,早在.NET2.0时代,基础类库中就提供了Thread实现。默认情况下,实例化一个Thread创建的是前台线程,只要有前台线程在运行,应用程序的进程就一直处于运行状态,以控制台应用程序为例,在Main方法中实例化一个Thread,这个Main方法就会等待Thread线程执行完毕才退出。而对于后台线程,应用程序将不考虑其是否执行完毕,只要应用程序的主线程和前台线程执行完毕就可以退出,退出后所有的后台线程将被自动终止。来看代码应该更清楚一些:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程开始");

            //实例化Thread,默认创建前台线程
            Thread t1 = new Thread(Fun_1);
            t1.Start();

            //可以通过修改ThreadIsBackground,将其变为后台线程
            Thread t2 = new Thread(Fun_2) { IsBackground = true };
            t2.Start();

            Console.WriteLine("主线程结束");
        }

        static void Fun_1()
        {
            Thread.Sleep(500);
            Console.WriteLine("这是前台线程调用");
        }

        static void Fun_2()
        {
            Thread.Sleep(1500);
            Console.WriteLine("这是后台线程调用");
        }
    }
}

结果: 控制台输出: 主线程开始 主线程结束 这是前台线程调用 由于前台线程一旦结束,后台线程强制结束,因此就不会显示“这是后台线程调用”。

了解多线程模型Task

了解基本使用方法:

//Task启动的是后台线程,假如要在主线程中等待后台线程执行完毕,可以调用Wait方法
    Task task = Task.Run(() => { Thread.Sleep(500); Console.WriteLine("Task2启动"); });
    task.Wait();

TaskThread的一个重要区分点是: Task底层是使用线程池的,而Thread每次实例化都会创建一个新的线程。

Task<TResult>Task的泛型版本,这两个之间的最大不同是Task<TResult>可以有一个返回值。

Task<string> task = Task<string>.Run(() => { Thread.Sleep(1000); return Thread.CurrentThread.ManagedThreadId.ToString(); });
Console.WriteLine(task.Result);

Task<TResult>的实例对象有一个Result属性,当在Main方法中调用task.Result的时候,将等待task执行完毕并得到返回值,这里的效果跟调用task.Wait()是一样的,只是多了一个返回值。

async/await

首先来看一下async关键字。async用来修饰方法,表明这个方法是异步的,声明的方法的返回类型必须为:voidTask或Task<TResult>。返回类型为Task的异步方法中无需使用return返回值,而返回类型为Task<TResult>的异步方法中必须使用return返回一个TResult的值

再来看一下await关键字。await必须用来修饰TaskTask<TResult>,而且只能出现在已经用async关键字修饰的异步方法中。 通常情况下,async/await必须成对出现才有意义,假如一个方法声明为async,但却没有使用await关键字,则这个方法在执行的时候就被当作同步方法,这时编译器也会抛出警告提示async修饰的方法中没有使用await,将被作为同步方法使用。

参考资料