进程是用力隔离不同的应用程序,线程是用来在一个进程中实现多个任务
一、进程(Process)
进程是Windows系统中的一个基本概念,它包含这一个运行程序所需要的资源,进程之间是相对独立的,一个进程无法直接访问另外一个进程的数据(除非利用分布式计算方式)一个进程运行的失败也不会影响其他的进程的运行,windows系统就是利用进行吧一个工作划分为多个独立的区域的。所以,所有的进程地位都是相同的,每个进程都有一个地址空间,和一个控制线程
一个程序可以看做是一个进程,一个进程中至少有一个线程,同一个进程中的多个线程之间可以“并发”执行,【一个进程就是一个正在执行的程序的实例,它包含着一个运行程序所需要的资源,进程之间是相对独立的。】
进程是一个资源的拥有者(即:一个程序跑起来后,里面的图片,代码等等都存在于进程当中,但是进程当中真正做事情的是线程),因而在进程的创建,撤销,和切换的过程中,系统必须为之付出较大的时空开销,限制了并发程度的进一步提高。
二、应用程序域 AppDomain
使用.NET建立的可执行程序 *.exe,并没有直接承载到进程当中,而是承载到应用程序域(AppDomain)当中。应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个轻量级的进程。
在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(*.exe)或者多个程序集(*.dll)。这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。
当一个程序集同时被多个应用程序域调用时,会出现两种情况:
第一种情况:CLR分别为不同的应用程序域加载此程序集。
第二种情况:CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutral。
AppDomain是CLR的运行单元,它可以加载Assembly、创建对象以及执行程序
AppDomain是CLR实现代码隔离的基本机制。
每一个AppDomain可以单独运行、停止;每个AppDomain有自己默认的异常处理;
一个AppDomain的运行失败不会影响到其他的AppDomain。
CLR在被CLR Host(windows shell or InternetExplorer or SQL Server)加载后,要创建一个默认的AppDomain,程序的入口点(Main方法)就是在这个默认的AppDomain中执行
AppDomain被创建在进程中,一个进程内可以有多个AppDomain。一个AppDomain只能属于一个进程。
AppDomain是个静态概念,只是限定了对象的边界;线程是个动态概念,它可以运行在不同的AppDomain中。
一个AppDomain内可以创建多个线程,但是不能限定这些线程只能在本AppDomain内执行代码。
CLR中的System.Threading.Thread对象其实是个soft thread,它并不能被操作系统识别;操作系统能识别的是hard thread。
一个soft thread只属于一个AppDomain,穿越AppDomain的是hard thread。当hard thread访问到某个AppDomain时,一个AppDomain就会为之产生一个soft thread。
hard thread有thread local storage(TLS),这个存储区被CLR用来存储这个hard thread当前对应的AppDomain引用以及soft thread引用。当一个hard thread穿越到另外一个AppDomain时,TLS中的这些引用也会改变。
Assembly是.Net程序的基本部署单元,它可以为CLR提供用于识别类型的元数据等等。Assembly不能单独执行,它必须被加载到AppDomain中,然后由AppDomain创建程序集中的对象。一个Assembly可以被多个AppDomain加载,一个AppDomain可以加载多个Assembly。
每个AppDomain引用到某个类型的时候需要把相应的assembly在各自的AppDomain中初始化。因此,每个AppDomain会单独保持一个类的静态变量。
三、线程(Thread)
线程是windows任务(cpu)调度的最小单位。线程是进程中一个执行流,每个线程都有自己的专有寄存器(栈指针,程序计数器(下一条要执行的指令)等),但代码区是共享的,即不同的线程可以执行同样的函数,一个进程中的多个线程亦可以共享该进程中的数据。
每个线程都只有极少的运行时间(在Windows内核模式下这个时间不会超过20ms)而当时间用完时,该线程就会被强制暂停,保存上下文并会把CPU运行权利交给下一个线程,这样调度的结果就是所有的线程都在被快速的切换运行,使得使用者感觉所有的线程在“并行”运行
使用多线程的理由:
1>每个进程都要执行,所以一个进程至少有一个线程
2>某些操作会被阻塞,所以要使用多线程
3>创建一个线程要比创建一个进程快的多,线程见切换也比进程间切换快。
4>合理使用多线程能大大提高应用程序性能(举例:如果多个任务都是CPU密集型的则使用多线程并不能提高性能,反而会性能下降)
5>在多CPU的系统中使用多线程是有益的,实现了真正的并发执行。查看“任务管理器”每个进程中的线程个数
6>在一个进程中的多个任务有时候是不能通过在创建多个进程实现的,因为要数据共享【进程用于把资源集中到一起,而线程则是CPU上被调度执行的实体】线程之间是“平等”的,不存在父子关系,每个线程都有一个唯一的编号
应用程序域(AppDomain)
它提供安全而通用的处理单元,公共语言运行库可使用它来提供应用程序之间的隔离,您可以在具有同等隔离级别(存在与单独的进程中) 的单个进程中运行几个应用程序域,而不会造成进程间调用货进程间切换,等方面的额外开销。优势:在一个应用程序中出现的错误不会影响其他应用程序,能够在不停止整个进程的情况下停止单个一用程序,一用程序形成了托管代码的隔离,卸载和安全边界。在任意给定时间,每一线程都在一个应用程序域中执行
前台线程和后台线程
InvokeHelper:跨线程访问/修改主界面控件方法、属性
C#线程用法及跨线程访问
进程
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace 进程Process
{
/// <summary>
/// Process类是一个非静态类。它里面包含静态成员和非静态成员。静态成员有类直接调用,非静态成员有类的对象来调用
/// </summary>
class Program
{
static void Main(string[] args)
{
//每一个应用程序都是一个进程。在.net中操作进程的这个类叫做Process
Process[] pros = Process.GetProcesses(); //获取当前计算机中正在运行的所有进程。存放到一个进程数组中(Process[] pros)
foreach (var item in pros)
{
//item.Kill();//立即停止当前进程。即:杀死当前进程。这个不能乱用啊。写在这个循环里面,就杀掉了所有正在运行的进程了,MD用了一次给我电脑都关闭了,太危险了。
Console.WriteLine(item); //将每一个进程输出到控制台。
}
//Start()表示启动一个进程
//通过进程来打开一些应用程序 //通过指定文档或应用程序文件的名称来启动进程资源:参数是:要在进程中运行的文档或应用程序文件的名称。
Process.Start("calc"); //打开计算器
Process.Start("mspaint"); //打开绘图工具
Process p1 = Process.Start("iexplore","Http://www.baidu.com"); //打开浏览器,打开百度
p1.Kill();//停止p1这个进程
//通过进程来打开一些文件
Process p = new Process();
//并指定启动进程时使用的诸如应用程序或文档的文件名。
ProcessStartInfo psi = new ProcessStartInfo(@"C:\Program Files (x86)\Tencent\QQ\QQProtect\Bin\QQProtect.exe");
//StartInfo:它表示启动该进程时要使用的数据
p.StartInfo=psi;
p.Start(); //启动进程
}
}
}
线程
默认我们创建的线程都是前台线程,一个进程什么时候结束呢?当所有前台线程都执行完毕以后,进程自动结束
,换句话说,只要有一个前台线程在执行,进程无论为何也不会结束(退出)
而对于后台线程,应用程序则可以不考虑这个后台线线程是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。
当前进程中所有“前台线程”执行完毕以后,后台线程自动关闭退出进程(即:前台进程没有了,后台线程就没有了)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Thread线程
{
/// <summary>
/// 当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main
/// Thread),因为它是程序开始时就执行的,如果你需要再创建线程,那么创建的线程就是这个主线程的子线程。
///
/// 一个进程是由多个线程组成的。线程分为两种,一种叫前台线程。另一种叫后台线程。
/// 前台线程:应用程序必须运行完所有的前台线程才可以退出;(主线程就是属于前台线程)
/// 后台线程:而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。
///
/// </summary>
class Program
{
public static void Test()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
//单线程的也就是程序执行时,所跑的程序路径(处理的东西)是连续顺序下来的,必须前面的处理好,后面的才会执行到。(这样容易发生“假死”等问题。例如:一个前台线程没有执行完毕,程序怎么都无法退出,卡在那里不动,造成假死,非要等前台线程都执行完毕了,才能退出程序。又例如:一个控制台程序,程序一开始便启动了10个线程,每个线程运行5秒钟时间。由于线程的IsBackground属性默认为false,即它们都是前台线程,所以尽管程序的主线程很快就运行结束了,但程序要到所有已启动的线程都运行完毕才会结束)
//而多线程的目的就是让计算机“同时”做多件事,节约时间。多线程是一种提高程序运行效率和性能的常用技术
static void Main(string[] args)
{
Console.WriteLine("我现在还不想吃饭"); //主线程完成输出。--这是主线程干的事
//创建一个线程。让这个线程去执行Test()这个方法。(注意Test这个方法不要括号)
//默认情况下我们创建的这个线程叫“前台线程”。我们也可以将他设为“后台线程”
Thread th = new Thread(Test); //--这是th这个线程干的事
th.IsBackground = true;//将刚刚创建的th这个线程设为“后台线程”
th.Start();//标记这个线程准备就绪了,可以随时被CPU执行(线程什么时候去执行有CPU决定,我们是不能手动的去调用线程的,我们能做的就是告诉CPU,这个线程已经准备好了,你可以随时执行它。如果此时CPU很忙的话它依然执行不了这个线程,如果不忙的话就可能马上来执行这个线程)
Console.WriteLine("我要吃饭"); //主线程完成输出 --这是主线程干的事
for (int i = 101; i < 200; i++)
{
Console.WriteLine(i); //主线程完成输出 --这是主线程干的事
}
//在.net下是不允许跨线程访问的。解决跨线程访问的方法是:Control.CheckForIllegalCrossThreadCalls = false;
//这个CheckForIllegalCrossThreadCalls属性指示是否捕获对错误线程的调用,如果false 就表示不检查对错误线程的调用。如果是true就表示检查对错误线程的调用。【这个Control类是:WinForm中所有控件的基类】
Thread.Sleep(3000);//让当前线程停止3秒后再运行。
Console.WriteLine("Hellow world");
}
}
}
线程类构造函数的的参数
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Appli
{
class Program
{
public static void Do()
{
Console.WriteLine("我是一个无参数的方法!");
}
public static void Dod(object name)
{
Console.WriteLine("我是一个有参数的方法,我叫"+name);
}
static void Main(string[] args)
{
//Thread类有四个构造函数(我们重点说一下其中的两个)
//第一个构造函数的参数:ThreadStart start
//public delegate void ThreadStart(); 我们可以看到这个ThreadStart()委托是一个无参数,无返回值的委托
//第二个构造函数的参数:ParameterizedThreadStart start
//public delegate void ParameterizedThreadStart(object obj); 我们可以看到ParameterizedThreadStart()委托是一个带一个参数,无返回值的委托
//创建一个线程。让这个线程去执行Do()这个方法。(注意Do这个方法不要括号)
Thread th = new Thread(new ThreadStart(Do));
//但是我们也可以这么写:
Thread th1 = new Thread(Do);
//其实就是将new ThreadStart()省略掉了。因为委托容许我们这么简写:比如
//public delegate void Mydel();
//Mydel=new Mydel() 这段可以简写成Mydel=Do ;因为编译器自动给我们完成了MyDelegate md = new MyDelegate(Do);
ParameterizedThreadStart start = new ParameterizedThreadStart(Dod);
Thread th2 = new Thread(start);
}
}
}
线程使用带参数的委托 (获取线程ID)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Appli
{
class Program
{
public static void Do()
{
Console.WriteLine("我是一个无参数的方法!");
// //获取当前正在运行的线程的线程ID
Console.WriteLine("我是th1" +"线程ID为:"+ Thread.CurrentThread.ManagedThreadId.ToString());
}
public static void Do(object name)
{
Console.WriteLine("我是一个有参数的方法,我叫"+name);
//获取当前正在运行的线程的线程ID
Console.WriteLine("我是th2 "+"线程ID为" + Thread.CurrentThread.ManagedThreadId.ToString());
}
static void Main(string[] args)
{
//Thread类有四个构造函数(我们重点说一下其中的两个)
//第一个构造函数的参数:ThreadStart start
//public delegate void ThreadStart(); 我们可以看到这个ThreadStart()委托是一个无参数,无返回值的委托
//第二个构造函数的参数:ParameterizedThreadStart start
//public delegate void ParameterizedThreadStart(object obj); 我们可以看到ParameterizedThreadStart()委托是一个带一个参数,无返回值的委托
//创建一个线程。让这个线程去执行Do()这个方法。(注意Do这个方法不要括号)
Thread th = new Thread(new ThreadStart(Do));
th.Start();
//但是我们也可以这么简写:(但是得注意:如果这个Do方法存在重载方法,那么就不能这么写,得按上面的详写方式)
//Thread th1 = new Thread(Do);
//th1.Start();
//简写其实就是将new ThreadStart()省略掉了。因为委托容许我们这么简写:比如
//public delegate void Mydel();
//Mydel=new Mydel() 这段可以简写成Mydel=Do ;因为编译器自动给我们完成了MyDelegate md = new MyDelegate(Do);
//-----------下面我们来看看线程使用带一个参数的委托
ParameterizedThreadStart start = new ParameterizedThreadStart(Do);
Thread th2 = new Thread(start);
th2.Start("张三"); //Do方法的参数写在这里
//获取当前正在运行的线程的线程ID
var thID = Thread.CurrentThread.ManagedThreadId.ToString();
//-----------下面我在看一种: 线程使用无参数的委托,但是实现带参数委托的功能:(把方法封装一下)
//思路:把要执行的方法封装成一个类
//把原本方法的参数,变成类的属性
//在方法中使用参数的地方都用属性来替换
//在调用该方法的时候,为属性赋值,即为参数赋值
MyClass c=new MyClass();
c.Number=100;
Thread th3 = new Thread(new ThreadStart(c.Sum));
th3.Start();
Console.ReadKey();
}
}
public class MyClass
{
public int Number { get; set; }
public void Sum()
{
int sum = 0;
for (int i = 0; i < this.Number; i++)
{
sum += i;
}
Console.WriteLine(sum);
}
}
}
线程使用带返回值的委托(方法)---如果获取委托的执行结果(返回值)
这里我们使用一个BackgroundWorker类,这里类的内部帮我们封装了一个线程,我们这里就用只管用这个BackgroundWorker类就可以了
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Appli
{
class Program
{
static int Sum(int from, int to)//求:从from 到 to的和
{
int sum = 0;
for (int i = from; i <= to; i++)
{
sum += i;
}
return sum;
}
//创建要在后台执行的事件 (在事件里执行方法)
static void MybacckWorker_DoWork(object sender, DoWorkEventArgs e)
{
//e.Argument表示获取异步操作参数的值
if (e.Argument != null) //如果值不为空
{
int[] arr = e.Argument as int[]; //将这个参数转换成int数组类型
e.Result = Sum(arr[0], arr[1]); //将Sum方法的返回值赋给e.Result
}
}
//创建当后台方法执行完毕后触发的事件
static void MybackWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("线程使用带返回值的的委托的返回值是:" + e.Result);
}
static void Main(string[] args)
{
//--------线程使用带返回值的委托
//我们启动线程以后,线程就开始执行了,线程执行完毕后,就自动结束了,可是我们并不知道线程什么时候结束,所以如果线程使用带返回值的委托(方法),就无法拿到这个委托(方法)的返回值。所以我们就可以用另外一种办法实现
//通过BackgroundWorker就可拿到线程使用带返回值的委托(方法)的返回值
//创建一个后台执行者对象(其实就是BackgroundWorker这个类内部帮我们封装了一个线程)
BackgroundWorker backWorkder = new BackgroundWorker();
//设定该对象要在后台执行的方法
backWorkder.DoWork += new DoWorkEventHandler(MybacckWorker_DoWork);
//设置另外一个事件:当程序执行完毕后触发的事件
backWorkder.RunWorkerCompleted += new RunWorkerCompletedEventHandler(MybackWorker_RunWorkerCompleted);
//开始执行后台操作
backWorkder.RunWorkerAsync(new int[] { 1, 100 }); //因为它的参数只有一个object类型,所以我们穿两个参数,就应该写搞数组,将参数放到数组里
Console.ReadKey();
}
}
}
执行结果图:
控件的跨线程访问
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//不要用Control.CheckForIllegalCrossThreadCalls = false;这种方式了!!!!!!!
//是否检查出跨线程的控件访问,设置为false,表示可以直接跨线程访问(不报异常)
// Control.CheckForIllegalCrossThreadCalls = false;
}
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "你好";
}
private void button2_Click(object sender, EventArgs e)
{
//如果在这直接使用这个textBox1控件,则是在t1线程中来访问textBox1控件,这样是不允许的。会报错:线程间操作无效: 从不是创建控件“textBox1”的线程访问它。
/*
Thread t1=new Thread(()=>{
this.textBox1.Text="我好";
});
t1.IsBackground = true;
t1.Start();
*/
//解决思路:将操作控件的代码,不要在t1线程中执行,把操作控件的代码到到主线程中去执行
//那如何把this.textBox1.Text="我好";这就代码在主线程中去执行呢?
//任何控件的Invoke()中方法表示将指定的代码在创建的线程中去执行
//这个Invoke()方法有两个意思:
//1,找到创建textBox1控件的线程
//2,把Invoke()的参数(就是一个委托)设置在创建该控件的线程上执行(Invoke方法的第二个参数,其实就是第一个参数(委托)指向的方法的参数,即:UpdataTextBox方法的参数)
//再次注释下:Invoke(参数1,参数2) 参数1表示要在创建该控件的线程上执行的委托。参数2是一个可变参数,表示要执行的委托指向的方法的参数列表。要执行的方法有几个参数则传递几个值。
this.textBox1.Invoke (new Action<string>(UpdataTextBox),"大家好");
}
public void UpdataTextBox(string msg)
{
this.textBox1.Text = msg;
}
}
}
Join() 线程的阻塞 (即:等一个线程执行完毕,在执行“主线程”的代码。或者说,等一个线程执行完毕,在执行其他的代码)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(new ThreadStart(() =>
{
for (int i = 1; i < 5; i++) {
Console.WriteLine(".");
Thread.Sleep(2000);
}
}));
t.Start();
Console.WriteLine("主线程继续执行");
for (int i = 0; i < 5; i++)
{
Console.WriteLine("=");
Thread.Sleep(1000);
}
Console.WriteLine("主线程执行完毕");
Console.ReadKey();
}
}
}
下面看一段写了线程阻塞的代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(new ThreadStart(() =>
{
for (int i = 1; i < 5; i++) {
Console.WriteLine("子线程 t 执行");
Thread.Sleep(2000);//让当前线程休眠2秒
}
}));
t.Start();
//t.Join()的意思就是:哪个线程正在执行,遇到t.join就会被阻塞,那个线程要等到t线程执行完毕,然后才会继续执行
t.Join();//阻塞当前“主线程”,等待t线程执行完毕后,继续主线程的执行
//Join 方法还有一个重载 1000表示我只阻塞1000毫秒(不管t这个线程是否执行完毕,超过1000毫秒我就不阻塞了)。
//t.Join(1000);
//其实Join()与Sleep()都是等待。
Console.WriteLine("主线程继续执行");
for (int i = 0; i < 5; i++)
{
Console.WriteLine("=");
Thread.Sleep(1000);//让当前线程休眠1秒
}
Console.WriteLine("主线程执行完毕");
Console.ReadKey();
}
}
}
线程的终止--优先级-- 取名 | 线程除了有ID号,还可以有名字
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
//一般情况不要设置线程的优先级,最后是否优先,还是以CPU的调度为准
Thread.CurrentThread.Priority = ThreadPriority.Highest; //将当前线程的优先级设为最高; 注:ThreadPriority是一个枚举
Thread t = new Thread(new ThreadStart(() =>
{
}));
t.Name = "我的线程"; //设置t这个线程的名字 (一般情况下我们不用设置线程的名字,这里主要是演示一下,可以设置线程的名字而已)
t.Priority = ThreadPriority.Lowest; //将t这个线程的优先级设为最低
t.Start();
var a= Thread.CurrentThread;// 获取当前正在执行的这个线程;
//线程的终止 (线程终止后,线程的这个t对象还是有的,只是这个t线程被停止了而已,线程终止以后就不能在启用了,如果想在使用的话,需要在new一个对象了)
//我们正常线程是不会随便就终止一个线程的,我们会有一个回调函数,等它调完以后,拿到它的方法返回值,或者接着处理一些其他程序,一般会等待这个线程执行结束,一般很少会强行终止一个线程
//t.Abort()方法是强行终止一个线程(一个线程正在执行的时候,谁知道正在执行到什么关键位置,你突然就给人家终止了,所以它会引发一个”mscorlib.dll类型的异常,但是这个异常不会引发到我们应用程序里面,仅仅是它内部的一个异常而已“)
//t.Abort(); //停止这个t线程; 注:线程停止后,就无法在启动了。如果想再用,就需要在new一下新的线程
Console.WriteLine("主线程执行完毕");
Console.ReadKey();
}
}
}
文件拷贝,带进度条 (跨线程的使用实例)
跨线程的访问:如果报错:线程间操作无效: 从不是创建控件“textBox1”的线程访问它。 一般是指在非创建textBox1这个控件的线程中,即其他线程中给textBox1这个控件赋值。如果是在非创建textBox1的线程中,即其他线程中给textBox1的值赋给其他的一些变量,则不会报“线程间操作无效....”这个错误。(控件是一般是由主线程创建)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace 文件拷贝带进度条
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void InitializeProgressBar()
{
this.progressBar1.Maximum = 100;
this.progressBar1.Minimum = 0;
this.progressBar1.Value = 0;
}
private void button1_Click(object sender, EventArgs e)
{
//初始化进度条
InitializeProgressBar();
//创建一个独立的线程池来指向文件拷贝操作,这样就可以防止阻塞主线程了
Thread t = new Thread(new ThreadStart(() =>
{
string source = textBox1.Text.Trim();
string target = textBox2.Text.Trim();
FileCopy(source, target);
}));
t.Start();
//拷贝文件
}
/// <summary>
/// 封装一个文件拷贝的方法
/// </summary>
/// <param name="source">拷贝的源文件路径</param>
/// <param name="target">将源文件拷贝到哪里</param>
private void FileCopy(string source, string target)
{
using (FileStream fsRead = new FileStream(source, FileMode.Open))
{
using (FileStream fsWrite = new FileStream(target, FileMode.Create))
{
byte[] buffers = new byte[1024 * 1024 * 10];//缓存区可以存放10M数据
//读取一次数据,每次读取10M,然后存入buffers这个数组中
int r = fsRead.Read(buffers, 0, buffers.Length); //r是实际读取到实际数据
while (r > 0)
{
//fsWrite.Position是获取或设置fsWrite这个流的当前位置,然后乘以1.0意思是表示将这个当前位置转换成双精度浮点数
// fsRead.Length 是获取这个source文件的总的长度
//当前位置除以总长度之后为什么要乘以100呢?那是因为已经将进度条最大长度设为100了。而当前位置除以总长度是小数,乘以100则是100以内的正数了。
//当然,如果不乘以100也可以啊,只要将进度条的最大长度设为1 最小长度设为0就可以啦。其实就是一个百分制的问题。
var val = (int)(fsWrite.Position * 1.0 / fsRead.Length * 100);
fsWrite.Write(buffers, 0, r);
//分开的写法
//Action<int> action = p => progressBar1.Value = p;
//this.progressBar1.Invoke(action, val);
//或者这种写法
this.progressBar1.Invoke(new Action<int>(v =>
{
progressBar1.Value = v;
}), val);
r = fsRead.Read(buffers, 0, buffers.Length);
}
}
}
MessageBox.Show("拷贝完毕");
}
}
}