Bootstrap

多线程(一)——委托与多线程

委托的使用

委托的概念,简单匿名函数与lamda表达式使用

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用语句,同时使得程序具有更好的可扩展性。
把一个方法作为参数传给另一个方法
委托内部由三部分:1,_methodPtr,是一个方法指针,指向当前委托指向的方法的内存地址;
2,_target,目标对象,如下

Program p=new Program();
del += p.AddStaticFunc1;

中,方法指针指向的是AddStaticFunc1方法,目标对象就是p
3、委托链,就是一个委托数组,定义委托实例AddDel del = new AddDel(AddStaticFunc);的背后就是创建这三个部分,当指向的方法为静态方法的时候,_target为null。
执行的时候,从委托链的第0个索引到最后一个索引,因此多播委托返回的是最后一个方法的执行结果。

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

namespace 委托概念
{
    internal class Program
    {
        //声明一个委托指向一个函数
        //委托指向的参数必须跟委托具有相同的签名
        public delegate void DelSayHi(string name);
        static void Main(string[] args)
        {
            //DelSayHi del = English;//new DelSayHi(English);
            //del("张三");
            //test("张三",Chinese);
            //test("李四", English);
            /*DelSayHi del=delegate (string s)
             {
                 Console.WriteLine("中文" + s);
             };*/
            DelSayHi del = (string s)=>
            {
                Console.WriteLine("中文" + s);
            };
            //lamda表达式,匿名函数的简写形式 => goes to
            del("张三");
            Console.ReadKey();
        }
        public static void test(string name, DelSayHi del)
        {
            del(name);
        }
        public static void Chinese(string name)
        {
            Console.WriteLine("中文"+name);
        }
        public static void English(string name)
        {
            Console.WriteLine("英文" + name);
        }
    }
}

委托的定义与使用

委托的定义与使用:
代码1:

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

namespace ConsoleApp1
{
    //定义一个委托类型
    delegate int AddDel(int a, int b);
    internal class Program
    {
        static void Main(string[] args)
        {
            //定义一个委托的实例
            AddDel del = new AddDel(AddStaticFunc);
            //触发实例
            int r = del(3, 4);
            Console.WriteLine(r);
            Console.ReadKey();
        }
        //定义一个符合委托规范的方法
        public static int AddStaticFunc(int a,int b)
        {
            return a + b;
        }
    }
}

结果输出:7
代码2:
匿名函数使用

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

namespace 委托
{
    internal class Program
    {
        public delegate string DelProStr(string name);
        static void Main(string[] args)
        {// ProStrSYH(names);
            string[] names = { "abcdEFG", "HIjklMn" };

            ProStr(names, delegate(string name)
            {
                return name.ToUpper();
            });
            //匿名函数,用delegate代替,只执行一次用
            for (int i = 0; i < names.Length; i++)
            {
                Console.WriteLine(names[i]);
            }
            Console.ReadKey();
        }

        public static void ProStr(string[] name,DelProStr del)
        {
            for (int i = 0; i < name.Length; i++)
            {
                name[i] = del(name[i]);
            }
        }
       /* public static string StrToUpper(string n)
        {
            return n.ToUpper();
        }
        public static string StrToLower(string n)
        {
            return n.ToLower();
        }
        public static string StrSYH(string name)
        {
            return "\"" + name + "\"";
        }*/
        /* public static void ProStrToUpper(string[] name)
         {
             for(int i = 0; i < name.Length; i++)
             {
                 name[i] = name[i].ToUpper();
             }
         }
         public static void ProStrToLower(string[] name)
         {
             for (int i = 0; i < name.Length; i++)
             {
                 name[i] = name[i].ToLower();
             }
         }
         public static void ProStrSYH(string[] name)
         {
             for (int i = 0; i < name.Length; i++)
             {
                 name[i] = "\"" + name[i]+ "\"";
             }
         }*/
    }
}

输出:
ABCDEFG
HIJKLMN

多播委托

多播委托1:

Program p=new Program();
del += p.AddStaticFunc1;
public int AddStaticFunc1(int a, int b)
{
    return a + b+1;
}

执行结果:8
使用多播委托时,拿到的委托返回值是最后一个委托指向的方法的执行结果。
多播委托2:

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

namespace 委托
{
    //可以指向多个函数
    public delegate void DelTest();
    internal class Program
    {
        static void Main(string[] args)
        {
            DelTest del = T1;
            del += T2;
            del -= T1;
            del();
            Console.ReadKey();
        }
        public static void T1()
        {
            Console.WriteLine("T1");
        }
        public static void T2()
        {
            Console.WriteLine("T2");
        }
        public static void T3()
        {
            Console.WriteLine("T3");
        }
        public static void T4()
        {
            Console.WriteLine("T4");
        }
    }
}

执行结果:T2 T3 T4

泛型委托

简单例子:

Func<int, int,int> funcDemo = new Func<int, int,int>(AddStaticFunc);
int result=funcDemo(1,2);
Console.WriteLine(result);
public static int AddStaticFunc(int a,int b)
{
    return a + b;
}

结果是3。
Func原构造函数:

public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

可以有很多个int,但是要有一个out,最后一项是用来约束返回值的。
在这里插入图片描述

匿名方法

Func<int, int,int> funcDemo = delegate(int a,int b) { return a + b; };

Lamda表达式

Func<int, int,int> funcDemo = (int a,int b)=> { return a + b; };Func<int, int,int> funcDemo = (int a,int b)=>  a + b;

例子:

public delegate void DelOne();
public delegate void DelTwo(string name);
public delegate string DelThree(string name);
static void Main(string[] args)
{
    //DelOne del = delegate () { };
    DelOne del = () => { };
    //DelTwo del2 = delegate(string name) { };
    DelTwo del2 = (string name) =>{};
    /*DelThree del3 = (string name) =>{
        return name;
    };*/
    DelThree del3 = delegate (string name)
    {
        return name;
    };
    List<int> list=new List<int> { 1, 2, 3 };
    list.RemoveAll(n => n > 4);
    //lamda表达式
}

举例

List<string> list = new List<string>()
{
    "3","9","32","7","2"
};
var temp = list.Where(delegate (string a)
{
    return a.CompareTo("6") < 0;
});
foreach (var v in temp)
{
    Console.WriteLine(v);
}
Console.ReadKey();

where是扩展方法,内部会遍历list集合,把每个元素传到委托里执行,如果返回true,就把元素选择出来,最后把所有满足条件的元素一起返回

扩展方法

三要素:静态类、静态方法、this关键字
重新定义where方法
定义:

public static class Class1
{
    public static List<T> myWhere<T>(this List<T> list,Func<T,bool> funcWhere)
    {
        List<T> result = new List<T>();
        foreach (var item in list)
        {
            if (funcWhere(item))
            {//调用委托,如果满足条件,加到集合中
                result.Add(item);
            }
        }
        return result;
    }
}

调用:

List<string> list = new List<string>(){ "3","9","32","7","2"};
匿名方法:
var temp = list.myWhere(delegate (string a)
{
    return a.CompareTo("6") < 0;
});
lamda表达式
 var temp = list.myWhere<string>(a => a.CompareTo("6") < 0);
方法泛型的约束可以省略,但是如果是显式的约束就必须满足约束。
即<string>可以省略不写,就像var demo = "sss";一样,编译器会自动推断类型
foreach (var v in temp)
{
    Console.WriteLine(v);
}
Console.ReadKey();

winform间窗体传值

传统方法

form1和form2两个类之间传值,在from1定义一个form2的对象,并将form2中文本框设置为外部可访问,如下所示。
form1:

public Form2 myForm2 { get; set; }
private void button2_Click(object sender, EventArgs e)
{
    myForm2.textBox2.Text = this.textBox1.Text;
}

form2的designer中:

public System.Windows.Forms.TextBox textBox2;

但是,对象内部的字段或者元素最好不要直接让外部访问,最好通过设置的方法控制一下。
例如,设置为私有后,在子窗体定义函数,父窗体中的子窗体对象调用函数:

public void SetText(string txt)
{
    textBox2.Text = txt;
}
myForm2.SetText(this.textBox1.Text);

使用委托

主窗体发送消息:点击按钮执行委托,给委托传递参数(当前文本框的内容),子窗体想接收就注册主窗体的委托,把子窗体的方法注册到委托中,执行时,委托执行每个子窗体的方法
主窗体发布,子窗体订阅
主窗体:点击按钮,执行委托

public Action<string> AfterMsgSend { get; set; }
public Form2 myForm2 { get; set; }
private void button2_Click(object sender, EventArgs e)
{
	 if(AfterMsgSend == null)
	 {
	     return;
	 }
	 AfterMsgSend(this.textBox1.Text);
}
private void Form1_Load(object sender, EventArgs e)
{
	 Form2 frm = new Form2();
	 myForm2 = frm;
	 //关注主窗体消息的变化
	 AfterMsgSend+= frm.SetText;
	 frm.Show();
}

SetText是Form2的一个方法

public void SetText(string txt)
{
    textBox2.Text = txt;
}

事件方式实现

//定义消息发布的事件
public event EventHandler AfterMsgEvent;
//EventHandler 是最常见的事件委托类型,子窗体需要符合它的规范
事件触发:
private void button2_Click(object sender, EventArgs e)
{
    AfterMsgEvent(this, new TextChangeEvent()
    {
        Text = this.textBox1.Text
    });
}
TextChangeEvent是一个类,继承事件,含有属性Text
public class TextChangeEvent:EventArgs 
{
    public string Text { get; set; }
}
//加载父窗体时的事件
AfterMsgEvent += frm.AfterParentFrmChange;
Form2中函数:
public void AfterParentFrmChange(object sender, EventArgs e)
{
    //拿到父窗体传过来的值
    TextChangeEvent text=e as TextChangeEvent;
    this.SetText(text.Text);
}

**区别:**委托是类型,事件是委托类型的一个特殊实例,事件只能在类的内部触发执行,安全

发布订阅模式的实现

发布订阅模式
观察者模式
指的是一个
发布订阅:子窗体的个数和类型对主窗体没有影响
如果不使用委托,在主窗体点击按钮,遍历集合,集合存放所有关心此主窗体消息变化的所有子窗体,这个集合相当于注册委托或事件方法
定义一个接口类型:

public interface Interface1
{
    void SetText(string text);
}

子窗体:

public partial class ChildFrm : Form,Interface1
{//继承基类,实现接口
    public ChildFrm()
    {
        InitializeComponent();
    }
    public void SetText(string text)
    {//实现接口的方法
        this.textBox1.Text = text;
    }
}

父窗体:

public partial class ParentFrm : Form
{
    public List<Interface1> ChildFrmList { get; set; }
    public ParentFrm()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        //遍历所有关注主窗体消息变化的子窗体集合
        //调用集合中每个元素的方法
        if(ChildFrmList==null) return;
        //减少括号的层次,比先判断不是空再循环要好一些
        foreach(var item in ChildFrmList)
        {
            item.SetText(this.textBox1.Text);
        }
    }

    private void ParentFrm_Load(object sender, EventArgs e)
    {
        ChildFrm frm=new ChildFrm();
        this.ChildFrmList=new List<Interface1>();
        this.ChildFrmList.Add(frm);
        frm.Show();
    }
}

如果再多加一个管理窗体,加载时的执行代码为:

//弹出主窗体
ParentFrm parentForm = new ParentFrm();
parentForm.Show();
ChildFrm childForm = new ChildFrm();
parentForm.ChildFrmList = new List<Interface1>();
parentForm.ChildFrmList.Add(childForm); 
childForm.Show();

这样,就实现了主窗体和子窗体的分离解耦。

文件流复习

选择文件并读取

using(OpenFileDialog ofd=new OpenFileDialog())
{
    if(ofd.ShowDialog() != DialogResult.OK)
    {
        return;
    }
    //选中文件后,读取文件
    //1、
    //string x=File.ReadAllText(ofd.FileName, Encoding.Default);
    //直接写,如果文件太大会卡死
    //2、使用文件流
    using(FileStream fs=new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read))
    {
        textBox1.Text = ofd.FileName;
        using(StreamReader sr=new StreamReader(fs))
        {
            while (!sr.EndOfStream)
            {
                string line=sr.ReadLine();
                this.textBox2.Text += line;
            }
        }
    }
}

写入文件

using(SaveFileDialog save=new SaveFileDialog())
{
    if(save.ShowDialog() != DialogResult.OK)
    {
        return;
    }
    //1、
    //File.WriteAllText(save.FileName,textBox2.Text,Encoding.Default);
    //2、
    /*using(StreamWriter sw=new StreamWriter(save.FileName,false,Encoding.Default,1024*1024))
    {//第二个参数是是否追加的意思,最后一个参数为缓冲区大小
        sw.Write(textBox2.Text);
        sw.Flush();//一举写入
    }*/
    //3、文件流写入
    using(FileStream fs=new FileStream(save.FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
    {
        string x=textBox2.Text.Replace("\n","\r\n");
        
        byte[]data=Encoding.Default.GetBytes(x);
        //不读取换行,一行行的读写,因为x没有\r
        //fs.Write(data,0,data.Length);
    }
}

多线程

一个线程就是一个进程里的代码执行流,每个线程都要指向一个方法体,方法执行完成之后,线程就释放。
例如:

Thread thread = new Thread
(delegate ()
{
    Console.WriteLine(DateTime.Now);
    Thread.Sleep(1000);
});
相当于
Thread thread = new Thread(ThreadMethod);
static void ThreadMethod()
{
    Console.WriteLine(DateTime.Now);
    Thread.Sleep(1000);
}

进程执行是调用Start()方法,线程默认是前台线程。尽量设置为true

thread.IsBackground = true;
thread.Start();

进程退出的标志:所有的前台线程都结束之后,后台线程不会阻塞进程的退出。
程序执行时,Main函数是程序的入口,CLR一开始就默认创建一个主线程,主线程也是一个前台线程。

Thread thread = new Thread(ThreadMethod);
thread.IsBackground = true;
thread.Start();
while (true)
{
    Console.WriteLine("from 主线程-----");
    Thread.Sleep(1000);
}
Console.ReadKey();

执行结果:
在这里插入图片描述
主线程与创建的线程不停的执行,类似于俩个线程同时在执行。
线程的其它属性:1、 IsAlive表示当前线程的状态,2、ManagedThreadId表示线程id
如下:

Console.WriteLine(thread.ManagedThreadId);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);  

3、优先级
设置为最高都是建议操作系统设置为最高,不一定为最高;线程执行是告诉操作系统可以执行,自己准备好了。

thread.Priority = ThreadPriority.Highest;

4、挂起:thread.Suspend();
5、继续挂起的线程:thread.Resume();
6、终止Abort,不得已才能用,直接终结:thread.Abort();
7、等待thread线程执行完成:thread.Join();,执行代码的线程等待thread执行完成,参数为超时时间。
例如:thread.Join(5000);后的代码执行结果为:
在这里插入图片描述

WinFrm与多线程

创建一个Windows窗体,设置为控制台输出
在这里插入图片描述
如果不使用线程,显示窗体线程就是前台线程,卡死;
如果设置了线程,使用lamda表达式输出内容,设置为前台线程,既可以控制台输出,主窗体也不卡死,关掉窗体,线程关闭。
如果不设置线程为前台线程,那么关闭主窗体,控制台仍然执行。

Thread thread = new Thread(() =>
{
    while(true)
       Console.WriteLine(DateTime.Now.ToString());
});
thread.IsBackground = true;
thread.Start();

跨线程访问控件

线程访问主线程(主窗体)控件,设置非法跨线程为false;但是这个属性只是演示,真正项目不可以使用。

Control.CheckForIllegalCrossThreadCalls = false;

正确访问方法:

Thread thread = new Thread(() =>
{
    if (button1.InvokeRequired)
    {//检查是否为别的线程创建的子控件,为true
        //找到创建控件的线程,执行方法
        button1.Invoke(new Action<string>(s => {
            this.button1.Text = s;
        }), DateTime.Now.ToString());
    }else   this.button1.Text = DateTime.Now.ToString();
    while(true)
       Console.WriteLine(DateTime.Now.ToString());
});
thread.IsBackground = true;
thread.Start();

进程与线程

操作系统分配的资源最小单位是进程,进程与进程之间互不影响,可以理解为一个程序的基本边界。
进程是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法直接访问另 一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域 的。进程可以理解为一个程序的基本边界。
CPU的其中一个核心,同一时间只能执行一个指令。
一个线程就是一个进程里面的代码的执行流。每个线程都要指向一个方法体,方法执行完成后线程就释放。
输出所有进程:

var allPro = Process.GetProcesses();
foreach (var pro in allPro)
{
    Console.WriteLine(pro.ProcessName + "\t" + pro.Id);
}

打开某个进程:

Process.Start("notepad", "aaa.txt");

第二个参数是文件名,如果打开浏览器,是网站地址

;