多线程主要用于多个任务并行执行,可以异步执行任务,提高响应速度,不阻塞当前线程(如C/S窗口)。

使用异步来调用以下代码:

     //用于委托调用
     private void DoSomething(string name)
     {
            Console.WriteLine($"DoSomething被调用 {name}");
     }

一、委托异步调用:.net core中似乎不支持了

C#使用多线程离不开委托,定义一个委托也是可以直接使用多线程的。

     Action<string> action = this.DoSomething;
     action.Invoke("张三来了"); //同步调用
     action("张三来了"); //效果与上面的一样

     AsyncCallback asyncCallback = result =>
     {
          Console.WriteLine($"asyncCallback被执行 {result.AsyncState}");
     };
     //参数一:委托方法的入参
     //参数二:异步执行完之后的回调函数(也是个委托)
     //参数三:需要传给回调函数的数据(object类型),用result.AsyncState来获取
     var asyncResult =   action.BeginInvoke("李四来了", asyncCallback,  "小二也来了");//异步调用
     action.EndInvoke(asyncResult);//阻塞异步,直到异步执行完成。如果是有返回值的异步调用(fun<>)。这里可以接收到。
     asyncResult.AsyncWaitHandle.WaitOne();//阻塞异步,直到异步执行完成。效果与上面一样,但是没有返回值
     asyncResult.AsyncWaitHandle.WaitOne(-1);//阻塞异步,直到异步执行完成。效果与上面一样
     asyncResult.AsyncWaitHandle.WaitOne(1000);//阻塞异步,但是只阻塞1000毫秒,超时就不等了
     bool isCompleted = asyncResult.IsCompleted;//异步是否执行完成,可以使用while()来循环等待,如下所示:
     int i = 0;
     while (!asyncResult.IsCompleted)
     {
          Thread.Sleep(200);
          if(i<9)
               Console.WriteLine($"操作正在进行中。。。已完成{(++i)*10}%");
           else
               Console.WriteLine($"操作正在进行中。。。已完成99.99%");
     }
     Console.WriteLine($"操作已完成");

二、Thread类库

Thread是C#对线程操作封装好的工具类。

int result = 0; //假如 DoSomething 有返回值,用于接收返回值: thread.Join(); 之后使用result。
Thread thread = new Thread(() => {
    DoSomething("王五来了");
    result = 5;
});
//不常用方法
//thread.Suspend();//暂停线程,不建议使用
//thread.Resume();//恢复赞停的线程,不建议使用
//thread.Abort(); //销毁线程,主线程让子线程抛出异常的方式来结束线程,可能会有延时,不一定能真的停下来。不建议使用
//Thread.ResetAbort();//恢复已销毁线程,不建议使用

//常用方法
while (thread.ThreadState != ThreadState.Stopped)//等待线程执行完成
{
    Thread.Sleep(200);
}
thread.Join();//运行这句代码的线程,等待thread完成。 可以在主线程等待,也可以在其它子线程等待
thread.Join(1000);//最多只等到1000ms
thread.Priority = ThreadPriority.Highest;//设置线程的执行优先级
//是否后台线程,false:非后台线程(进程关闭,子线程执行完才退出),true:是后台线程(进程关闭,子线程也退出)
thread.IsBackground = false;

//模拟异步回调:其实说白了就是异步执行完了再执行另一个函数。
Thread thread1 = new Thread(() => {
    DoSomething("王五来了");
    DoSomething("这是回调");
});

三、ThreadPool

线程池(ThreadPool),创建了多个线程对象,需要用的时候直接从线程池中取,避免了对象的创建和销毁等代价(享元设计模式)。节约资源 提升性能,控制线程数量,防止滥用。

ThreadPool.QueueUserWorkItem((obj) => DoSomething("王五来了") );

//委托的参数obj就是QueueUserWorkItem的第二个参数。
ThreadPool.QueueUserWorkItem((obj) => DoSomething($"王五来了({obj.ToString()})"),"王五的儿子");

//获取线程池中辅助线程的最大数量(workerThreadsMax)和线程池中异步I/O线程的最大数量(completionPortThreadsMax)
//获取线程池中辅助线程的最小数量(workerThreadsMin)和线程池中异步I/O线程的最小数量(completionPortThreadsMin)
ThreadPool.GetMaxThreads(out int workerThreadsMax, out int completionPortThreadsMax);
ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin);

//设置最大线程数量 和 设置最小线程数量,在进程内是全局的。在一个地方设置了,后面所有的请求中都是这个数量了
//委托异步调用、Task、Parallel、async/await 都使用的是线程池的线程; new Thread()不受限制,但是会占用线程池的数量。
ThreadPool.SetMaxThreads(12, 12);//不能低于当前电脑的线程数;比如四核八线程,就不能低于8,否则无效
ThreadPool.SetMinThreads(1, 1);

//线程等待,需要使用ManualResetEvent来完成
ManualResetEvent mre = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((obj) => {
    DoSomething("王五来了");
    mre.Set();
} );
mre.WaitOne();

四、Task

Task是基于任务的异步编程模型。使用的还是线程池里面的线程。

使用Task的时候应该尽量结合async和await关键字来使用。避免使用.Result 和 .Wait()来阻塞等待;

.Result 和 .Wait()会占用线程资源,直到任务完成。

而await的基于异步回调的,不会浪费CPU资源;async和await是语法糖,本质上其实是ContinueWith()。

//*********************************Task使用方式1*******************************************
Task task1 = new Task(() => { DoSomething("张三"); });
task1.Start();

//*********************************Task使用方式2*******************************************
var taskFactory = Task.Factory;
Task<int> t1 = taskFactory.StartNew<int>(() => { DoSomething("王五"); return 1; });
Task t2 = taskFactory.StartNew(() => { DoSomething("赵六"); });

Task t3 = taskFactory.ContinueWhenAll(new Task[] { t1, t2 }, (t) =>
 {
     Console.WriteLine("所有线程都完成了,就会调用这个函数, 不会阻塞主线程");
 });
Task t4 = taskFactory.ContinueWhenAny(new Task[] { t1, t2 }, (t) =>
{
    Console.WriteLine("任意一个线程完成了,就会调用这个函数, 不会阻塞主线程");
});

//*********************************Task使用方式3*******************************************
Task<int> task = Task.Run<int>(() => { DoSomething("李四"); return 1; }); //使用方式3
int temp = task.Result; //阻塞执行完毕并获取结果,不建议使用这种方式
task.Wait(); //阻塞,直到子线程执行完毕,不建议使用这种方式
await task; //阻塞,直到子线程执行完毕。 但是主线程(调用方)将继续往下执行,await task后面的代码等同于封装在ContinueWith()里面

 //*********************************Task其它使用方式*******************************************

 //Task.Delay(2000)不阻塞当前线程,一般配合ContinueWith使用,在ContinueWith里面的子线程将等待2秒之后执行
 //Thread.Sleep(2000)是阻塞当前线程
 Task task3 = Task.Delay(2000).ContinueWith((t) =>
{
});

//等待所有线程完成
Task.WaitAll(new Task[] { task1, t1 });
//等待任意一个线程完成
Task.WaitAny(new Task[] { task1, t1 });
//线程回调
task1.ContinueWith((o) =>
{
     Console.WriteLine("线程回调,task1执行完毕之后执行这里。");
});

五、async/await(C/S不建议使用)

async/await是C#保留关键字,需要结合Task使用,async/await是语法糖,本质上其实是ContinueWith()。

使用await Task可以充分的利用有限的CPU资源。在进行IO操作的时候应该尽量使用await Task。

async用于标识异步方法,方法内部有两种情况:

1、没有await:调用方降等待异步方法执行完毕才能接着往下执行,异步方法中的子线程还是并行执行的。

2、包含await:调用方执行异步方法,遇到await后,调用方可以立马往下执行,不需要等待异步方法执行完毕,这个时候“子线程和await task后面的代码”与“调用方”是并行执行的。这里的await指的是在创建子线程(Task.Run)的地方await,且调用方调用异步方法的时候前面没有加await。但是异步函数内部执行到await关键字后会等待子线程执行完毕。

      感觉就像是将await后面的代码封装到ContinueWith()里面执行一样。

使用Task.Run()会创建一个子线程。前面加await会把后面的代码封装成异步回调函数,也是一个新的子线程。使用.Result 或者.Wait()不会像await一样创建子线程。

GitHub 加速计划 / th / ThreadPool
7.74 K
2.22 K
下载
A simple C++11 Thread Pool implementation
最近提交(Master分支:2 个月前 )
9a42ec13 - 9 年前
fcc91415 - 9 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐