C#多线程
一多线程的几种方式
统一用于测试的模拟下载代码
private static void DownMethod(int id,string fileName)
{
Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, fileName);
for (int i = 0; i < 100; i+=10)
{
Thread.Sleep(10);
Console.WriteLine("已经下载{0}", i);
}
Console.WriteLine("下载完毕!!!");
}
1:委托创建线程实现异步
实例1:Action委托创建线程
//多线程的第一种方式Action委托没有返回值
public static void ThreadMethod(int id, string fileName)
{
DownMethod(id,fileName);
}
static void Main(string[] args)
{
//方法一:使用Action委托来异步执行方法
Action<int, string> fileDownAction = ThreadMethod;
//前面的参数是委托所需要的参数,后面两个是固定的,一个是线程结束回调,一个是传入回调的参数
fileDownAction.BeginInvoke(1, "天才在左疯子在右", ar =>
{
Console.WriteLine("线程结束回调");
Console.WriteLine(ar.AsyncState as string);//最后一个参数
fileDownAction.EndInvoke(ar);//这个方法用于获取此线程的返回值,由于Action委托没有返回值,所以不返回
}, "回调的参数");
Console.WriteLine("我是主线程");
Console.ReadKey();
}
结果
我是主线程
开始下载!文件ID:1,文件名字天才在左疯子在右
已经下载0
已经下载10
已经下载20
已经下载30
已经下载40
已经下载50
已经下载60
已经下载70
已经下载80
已经下载90
下载完毕!!!
线程结束回调
回调的参数
实例2:Func委托创建线程
public static string FuncThreadMethod(int id, string fileName)
{
DownMethod(id, fileName);
return "异步方法的返回值!!!";
}
static void Main(string[] args)
{
Func<int, string,string> fileDownAction = FuncThreadMethod;
//前面的参数是委托所需要的参数,后面两个是固定的,一个是线程结束回调,一个是传入回调的参数
fileDownAction.BeginInvoke(1, "天才在左疯子在右", ar =>
{
Console.WriteLine("线程结束回调");
Console.WriteLine(ar.AsyncState as string);//最后一个参数
string result = fileDownAction.EndInvoke(ar);//这个方法用于获取此线程的返回值,由于Action委托没有返回值,所以不返回
Console.WriteLine(result);
}, "回调的参数");
Console.WriteLine("我是主线程");
Console.ReadKey();
}
}
我是主线程
开始下载!文件ID:1,文件名字天才在左疯子在右
已经下载0
已经下载10
已经下载20
已经下载30
已经下载40
已经下载50
已经下载60
已经下载70
已经下载80
已经下载90
下载完毕!!!
线程结束回调
回调的参数
异步方法的返回值!!!
2:Thread创建异步线程
实例1:普通的方式
//使用Thread方式创建的委托方法一定没有返回值,参数可有可无,但是有参数的话一定是object类型的
public static void AsyncWithThreadMethod(object obj)
{
Console.WriteLine("开始下载!文件名字{0}", obj as string);
for (int i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已经下载{0}", i);
}
Console.WriteLine("下载完毕!!!");
}
static void Main(string[] args)
{
//Thread创建线程的方式,Thread的构造参数一定是个无返回值的委托
Thread thread = new Thread(AsyncWithThreadMethod);
thread.Start("天才在左疯子在右");
Console.WriteLine("我是主线程");
Console.ReadKey();
}
lambda表达式方式:
Thread thread = new Thread((obj) =>
{
Console.WriteLine("开始下载!文件名字{0}", obj as string);
for (var i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已经下载{0}", i);
}
Console.WriteLine("下载完毕!!!");
});
thread.Start("天才在左疯子在右");
但是这种方式如果想要传递一些复杂的或者多个参数就不好弄了,所以可以用另一种方式,自己新建一个类来处理
class MyThread
{
private int id;
private string name;
public MyThread(int id, string name)
{
this.id = id;
this.name = name;
}
private void DownFileStart(object callback)
{
Action<string> action = callback as Action<string>;
Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, name);
for (int i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已经下载{0}", i);
}
Console.WriteLine("下载完毕!!!");
action?.Invoke("下载完毕!!!!");
}
//这个方法使用回调来处理线程执行完毕的结果
public void Start(Action<string> callback)
{
Thread t = new Thread(DownFileStart);
t.Start(callback);
}
}
使用:
MyThread mt = new MyThread(1, "aaa");
mt.Start(o =>
{
Console.WriteLine("线程完成回调---->{0}", o);
});
Console.WriteLine("我是主线程");
Console.ReadKey();
结果:
我是主线程
开始下载!文件ID:1,文件名字天才在左疯子在右
已经下载0
已经下载10
已经下载20
已经下载30
已经下载40
已经下载50
已经下载60
已经下载70
已经下载80
已经下载90
下载完毕!!!
线程完成回调---->下载完毕!!!!
前台线程和后台线程
前台线程:Thread创建的线程默认都是前台线程,前台线程不会跟随主线程的停止而停止,如果主线程提前停止,程序会跟着前台线程的停止而停止
后台线程:使用线程池创建的线程都是后台线程,不会被更改,主线程停止了后台线程也就随之停止了,Thread可以设置IsBackground = true;来设置为后台线程
3:线程池创建线程
线程池类ThreadPool是静态类,无法被声明和new,只能通过类名来调用里面的静态方法
一般通过线程池创建线程的方法如下:
ThreadPool.QueueUserWorkItem(state =>
{
Console.WriteLine("创建线程池的线程,参数:{0},线程ID:{1}", state as string, Thread.CurrentThread.ManagedThreadId);
}, "11");
一般来说线程池用于处理多个耗时较为少的任务,不推荐处理长时间任务
4:任务(Task)
(模拟线程代码)
private static void DownMethod(int id, string fileName)
{
Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, fileName);
for (int i = 0; i < 100; i += 10)
{
Thread.Sleep(10);
Console.WriteLine("已经下载{0}", i);
}
Console.WriteLine("下载完毕!!!");
}
//多线程的第一种方式Action委托没有返回值
public static string ThreadMethod()
{
DownMethod(1, "天才在左疯子在右");
return "1111";
}
public static void SaveInFile(Task task)
{
Console.WriteLine("正在存储文件");
Thread.Sleep(1000);
Console.WriteLine("存储完毕!!!");
}
任务有两种创建方式
- 泛型代表返回值
Task<string> t = new Task<string>(ThreadMethod);
t.Start();
TaskFactory<string> tf = new TaskFactory<string>();
tf.StartNew(ThreadMethod);
连续任务
当任务1依赖于任务2的时候那就需要任务2执行完毕之后执行任务1,这个时候用连续任务
TaskFactory<string> tf = new TaskFactory<string>();
var startNew = tf.StartNew(ThreadMethod);
//主要是这句话
var startNew2 = startNew.ContinueWith(SaveInFile);
任务的层次结构
如果在一个任务中再次调用一个任务,那么内部这个任务就是外边的任务的子任务,如果子任务没有执行完毕而父任务执行完毕的话,则父任务的状态是WaitingForChildrenToComplete,只有子任务执行完了,父任务的状态就变成了RunToComplete
4:线程争用问题(同步锁)
测试类:
/// <summary>
/// 测试线程争用问题
/// </summary>
class MyThreadObject
{
//用于测试多线程争用同一变量的变量
private int state = 5;
public void ChangeState()
{
state++;
if (state == 5)
{
Console.WriteLine("state =5");//正常来讲是永远不会执行这一句的
}
state = 5;//当其中一个线程执行到这一句的时候另一个线程刚执行到if (state == 5),这时就会打印
}
}
主函数:
class Program
{
static void ChangeMyThreadState(object obj)
{
var myThread = obj as MyThreadObject;
while (true)
{
myThread?.ChangeState();
}
}
private static void Main(string[] args)
{
MyThreadObject myThread = new MyThreadObject();
Thread t1 = new Thread(ChangeMyThreadState);
t1.Start(myThread); //只有这一个线程是没有问题的
Thread t2 = new Thread(ChangeMyThreadState);
t2.Start(myThread); //加上这个线程就会出问题,
Console.WriteLine("我是主线程");
Console.ReadKey();
}
}
结果是打印了好多。
解决方法是加同步锁
static void ChangeMyThreadState(object obj)
{
var myThread = obj as MyThreadObject;
while (true)
{
if (myThread == null) continue;
lock (myThread) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行
{
myThread.ChangeState(); //在同一时刻只有一个线程执行
}
}
}
注意:lock只能锁定对象(引用类型)
4:线程死锁
class Program
{
static MyThreadObject myThread = new MyThreadObject();
static MyThreadObject myThread1 = new MyThreadObject();
static void ChangeMyThreadState(object obj)
{
while (true)
{
lock (myThread) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行
{
lock (myThread1)
{
myThread.ChangeState(); //在同一时刻只有一个线程执行
Console.WriteLine("0000");
}
}
}
}
static void ChangeMyThreadState1(object obj)
{
while (true)
{
lock (myThread1) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行
{
lock (myThread)
{
myThread.ChangeState(); //在同一时刻只有一个线程执行
Console.WriteLine("11111");
}
}
}
}
private static void Main(string[] args)
{
Thread t1 = new Thread(ChangeMyThreadState);
t1.Start(); //只有这一个线程是没有问题的
Thread t2 = new Thread(ChangeMyThreadState1);
t2.Start(); //加上这个线程就会出问题,
Console.WriteLine("我是主线程");
Console.ReadKey();
}
}
执行结果只打印了10几条,就停止了