一、进程与线程
进程是指一个程序在计算机上运行时,全部的计算资源的合集;
线程是程序的最小执行单位,包含计算资源,任何一个操作的响应都是线程来完成的;
多线程是指多个线程并发执行;
多线程虽然能够提升程序的运行效率,但是消耗的资源更多,所以线程并不是越多越好。
二、同步和异步
同步和异步都是针对方法而言;
同步方法指的是程序按照顺序执行,必须等待上一操作完成后才能继续往后执行;
异步方法指的是方法在调用之后立即返回,以便程序在该被调用方法返回后调用其它方法执行其他任务。
三、.Net框架中涉及异步多线程的知识
不要试图通过启动顺序或者时间等待来控制线程的执行的顺序,因为线程执行是启动无序执行时间不确定的,控制线程顺序的方法可以有三种: 回调、等待、状态判断
//首先定义一个耗时比较多的方法来做后续调用
private void DoSomething(string name)
{
Console.WriteLine($"******DoSomething Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}*****");
long lResult = 0;
for (int i = 0; i < 1000000000; i++)
{
lResult += i;
}
//Thread.Sleep(5000);
Console.WriteLine($"******DoSomething End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}*****");
}
//不带返回值的委托
{
Action act = this.DoSomething;
IAsyncResult iAsyncResult = null;
AsyncCallback callback = ar =>
{
Console.WriteLine(object.ReferenceEquals(ar, iAsyncResult));
Console.WriteLine(ar.AsyncState);
Console.WriteLine($"这里是BeginInvoke的回调函数。。。{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
};
/*
* BeginInvoke异步调用,会开启一个新的线程,不同于Invoke同步方法
* 此方法返回IAsyncResult类型的一个参数,实际上这个参数就是上面AsyncCallback委托的传入参数
* 该方法的第三个参数,会传给回调方法中的IAsyncResult类型的参数,将其放在AsyncState字段
* 这里就是利用回调的方式来控制线程的执行顺序
*/
asyncResult = act.BeginInvoke("委托类型的参数", callback, "lucas"); //也可以用下面表示
asyncResult = act.BeginInvoke("委托类型的参数", ar => {
Console.WriteLine(ar.AsyncState); //可以拿到传入的第三个参数
},"lucas");
/*通过状态来控制,缺点是 1、主线程需要等待 2、边等待边做事儿 3、有误差*/
while (!asyncResult.IsCompleted)
{
Thread.Sleep(200);
}
/*利用等待来控制线程的执行顺序*/
asyncResult.AsyncWaitHandle.WaitOne(); //等待子线程执行完主线程再继续执行
asyncResult.AsyncWaitHandle.WaitOne(-1);//同上
asyncResult.AsyncWaitHandle.WaitOne(1000);//等1秒钟后再执行
act.EndInvoke(asyncResult);//也是等待
Console.WriteLine("这里是BeginInvoke执行完毕之后才继续执行");
}
//带返回值的委托
{
Func func = i => i.ToString();
IAsyncResult iAsyncResult = func.BeginInvoke(DateTime.Now.Year, ar =>
{
string resultIn = func.EndInvoke(ar); //调用EndInvoke,获得返回值
}, "lucas");
}
{
ThreadStart threadStart = new ThreadStart(() => DoSomething("lucas"));
Thread thread = new Thread(threadStart);
thread.IsBackground = true; //若开启,则为后台线程,UI线程退出后,也跟着退出
thread.Start();//前台线程,UI线程退出后,还会继续执行完
}
{
/*
* Thread控制线程的顺序可以使用下面的方式
*/
thread.Join();//等待子线程执行完主线程再继续执行
while (thread.ThreadState != System.Threading.ThreadState.Running) //判断状态
{
}
#region 结合委托来实现回调
///
/// 定义一个基于Thread的回调,与BeginInvoke相似
///
///
///
private void CallbackByThread(ThreadStart threadStart, Action callback)
{
ThreadStart startNew = new ThreadStart(() =>
{
threadStart();
callback.Invoke(); //此处会等待threadStart执行完再执行
});
Thread thread = new Thread(startNew);
thread.Start();
}
///
/// 定义一个带返回值的回调
///
///
///
///
private Func ReturnByThread(Func funcT)
{
T t = default(T);
ThreadStart startNew = new ThreadStart(() =>
{
t = funcT.Invoke();
});
Thread thread = new Thread(startNew);
thread.Start();
return new Func(() => {
thread.Join(); //等待子线程执行完毕再继续执行
return t;
});
}
{
CallbackByThread(()=>{ Console.WriteLine("这里是新开的线程"); },()=>{ Console.WriteLine("这里是回调方法"); });
Func<string> func = ReturnByThread(()=>{ Console.WriteLine("新开线程执行,后续可以获取返回值"); });
string result = func.Invoke(); //获取到返回值,类似EndInvoke
}
#endregion
}
{
ThreadPool.QueueUserWorkItem(o => {
DoSomething("lucas");
}, "lucas");
ThreadPool.QueueUserWorkItem(o =>
{
DoSomething("lucas");
// 回调,再包一层
new Action(() => {
Console.WriteLine("这里是回调方法");
}).Invoke();
});
/*
* 线程池的执行顺序可以用 ManualResetEvent 来控制
* 任何时候如果需求不需要等待,则不要随便去设置这个
*/
ManualResetEvent mre = new ManualResetEvent(false); //关闭
ThreadPool.QueueUserWorkItem( o => {
Thread.Sleep(5000);
Console.WriteLine("");
mre.Set(); //此处调用Set之后变成true,WaitOne()才会继续往下执行
});
Console.WriteLine("WaitOne 之前");
mre.WaitOne(); // 需要set之后才会往后执行,如果没有Set,不会往后执行,再Set之后还可以Reset将其关闭,则会继续等待
Console.WriteLine("WaitOne 之后");
}
{
#region PoolSet
ThreadPool.SetMaxThreads(8, 8);
ThreadPool.SetMinThreads(8, 8);
int workerThreads = 0;
int ioThreads = 0;
ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("Max worker threads: {0}; Max I/O threads: {1}", workerThreads, ioThreads));
ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("Min worker threads: {0}; Min I/O threads: {1}", workerThreads, ioThreads));
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("Available worker threads: {0}; Available I/O threads: {1}", workerThreads, ioThreads));
#endregion
}
{
/*两种启动方式是一样的*/
Task.Run(new Action(() => {
Thread.Sleep(5000);
}));
Task.Factory.StartNew(() => { Console.WriteLine("");});
}
{
/*
* 需要多线程加快速度,同时又有要求全部完成后,才能返回
* 多业务操作,希望并发,但是全部完成后再返回
*/
TaskFactory taskFactory = Task.Factory;
List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(() => { }));
taskList.Add(taskFactory.StartNew(() => { }));
taskList.Add(taskFactory.StartNew(() => { }));
taskList.Add(taskFactory.StartNew(() => { }));
Task.WaitAll(taskList.ToArray()); //等待,任务全部完成后再往后执行
/*
* 需要多线程加快速度,同时又有要求某个完成后,才能返回
* 多业务操作,希望并发,但是任意某个任务完成后再返回
*/
Task.WaitAny(taskList.ToArray());
//想知道是哪个任务完成,可以传入第二个参数,然后通过ContinueWith来获得
Task task = taskFactory.StartNew(o => { }, "lucas");
//通过task.AsyncState 就是下面的 t的 AsyncSate来进行判断
Task task1 = taskFactory.StartNew(o => { }, "lucas").ContinueWith(t => Console.WriteLine($"这里是{t.AsyncState}的回调"));
//不阻塞主线程使用回调
taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId.ToString()}"));
taskFactory.ContinueWhenAll(taskList.ToArray(), tList => Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId.ToString()}"));
//带返回值的
Task<int> intTask = taskFactory.StartNew(() => 123);
int result = intTask.Result; //获得返回值
}
{
//跟task很像,等价于 task+waitall ,启动多个线程计算,而且主线程也参与计算,节约了一个线程
Parallel.Invoke(() => DoSomething("Task_001"),
() => DoSomething("Task_002"),
() => DoSomething("Task_003"),
() => DoSomething("Task_004"),
() => DoSomething("Task_005"));
Parallel.For(0, 5, t =>
{
DoSomething($"Task_00{t}");
});
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, t =>
{
DoSomething($"Task_00{t}");
});
ParallelOptions options = new ParallelOptions()
{
MaxDegreeOfParallelism = 3 //设置启动并发任务数量最多是3个
};
//只会开启3个线程执行,并且包含了主线程
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, t =>
{
DoSomething($"Task_00{t}");
});
//可以利用委托的第二个参数state来结束并行运算
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, options, (t, state) =>
{
DoSomething($"Task_00{t}");
//state.Stop();//结束全部的
//state.Break();//停止当前的
//return;
});
}
private static async void NoReturn()
{
//主线程执行
Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
TaskFactory taskFactory = new TaskFactory();
Task task = taskFactory.StartNew(() =>
{
Console.WriteLine($"NoReturn Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(3000);
Console.WriteLine($"NoReturn Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
await task;//主线程到这里就返回了,执行主线程任务
//一流水儿的写下去的,耗时任务就用await
//子线程执行 其实是封装成委托,在task之后成为回调(编译器功能 状态机实现)
//task.ContinueWith()
//这个回调的线程是不确定的:可能是主线程 可能是子线程 也可能是其他线程
Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}
///
/// 无返回值 async Task == async void
/// Task和Task 能够使用await, Task.WhenAny, Task.WhenAll等方式组合使用。Async Void 不行
///
///
private static async Task NoReturnTask()
{
//这里还是主线程的id
Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Task task = Task.Run(() =>
{
Console.WriteLine($"NoReturnTask Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(3000);
Console.WriteLine($"NoReturnTask Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
await task;
Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
//return new TaskFactory().StartNew(() => { }); //不能return 没有async才行
}
///
/// 带返回值的Task
/// 要使用返回值就一定要等子线程计算完毕
///
/// async 就只返回long
private static async Task<long> SumAsync()
{
Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
long result = 0;
await Task.Run(() =>
{
for (int k = 0; k < 10; k++)
{
Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
}
for (long i = 0; i < 999999999; i++)
{
result += i;
}
});
return result;
}
{
Task<long> t = SumAsync();
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
long lResult = t.Result;//访问result,主线程等待Task的完成
t.Wait();//等价于上一行
}
线程的异常处理
多线程里面的异常不会被外部线程捕获到,除非调用WaitAll进行等待,所以使用多线程的时候,是不允许抛出异常的,也就是说线程的异常需要在内部自己try/catch处理掉。
try{
TaskFactory taskFactory = new TaskFactory();
List taskList = new List();
for (int i = 0; i < 20; i++)
{
string name = string.Format($"btnThreadCore_Click_{i}");
Action<object> act = t =>
{
try
{
Thread.Sleep(2000);
if (t.ToString().Equals("btnThreadCore_Click_11"))
{
throw new Exception(string.Format($"{t} 执行失败"));
}
if (t.ToString().Equals("btnThreadCore_Click_12"))
{
throw new Exception(string.Format($"{t} 执行失败"));
}
Console.WriteLine("{0} 执行成功", t);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
};
taskList.Add(taskFactory.StartNew(act, name));
}
Task.WaitAll(taskList.ToArray()); //此处调用WaitAll后上面抛出的异常才能被捕获,否则无法捕获异常
}
catch(AggregateException aex)
{
foreach (var item in aex.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
TaskFactory taskFactory = new TaskFactory();
List taskList = new List();
//CancellationTokenSource可以在cancel后,取消没有启动的任务
CancellationTokenSource可以在cancel后,取消没有启动的任务
CancellationTokenSource cts = new CancellationTokenSource();//bool值
for (int i = 0; i < 40; i++)
{
string name = string.Format("btnThreadCore_Click{0}", i);
Action<object> act = t =>
{
try
{
Thread.Sleep(2000);
if (t.ToString().Equals("btnThreadCore_Click11"))
{
throw new Exception(string.Format("{0} 执行失败", t)); //抛异常做测试
}
if (t.ToString().Equals("btnThreadCore_Click12"))
{
throw new Exception(string.Format("{0} 执行失败", t));
}
if (cts.IsCancellationRequested)//检查信号量
{
Console.WriteLine("{0} 放弃执行", t);
return;
}
else
{
Console.WriteLine("{0} 执行成功", t);
}
}
catch (Exception ex)
{
cts.Cancel(); //这里抛异常后进行了Cancel,其他线程运行的时候检测到信号量便返回不再执行
Console.WriteLine(ex.Message);
}
};
taskList.Add(taskFactory.StartNew(act, name, cts.Token));
}
Task.WaitAll(taskList.ToArray());
private static readonly object LockObject= new object();
private int TotalCount = 0;//
private List<int> IntList = new List<int>();
for (int i = 0; i < 10000; i++)
{
int newI = i;
taskList.Add(taskFactory.StartNew(() =>
{
int m = 3 + 2;
lock (LockObject)//lock后的方法块,任意时刻只有一个线程可以进入
//Monitor.Enter(LockObject)
{
//这里就是单线程
this.TotalCount += 1;//多个线程同时操作,有时候操作被覆盖了
IntList.Add(newI);
}
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(this.TotalCount);
Console.WriteLine(IntList.Count());
四、 总结
1、使用多线程的时候,尽量不要是用Thread,ThreadPool
2、最好使用Task,Parallel,async/await
3、使用委托进行异步操作的时候,尽量不要自己声明委托了,因为微软已经提供了Action 、Func两种委托,足够使用了
4、使用线程的时候要注意异常的捕获,和线程安全问题