说起异步,thread,task,async/await,iasyncresult 这些东西肯定是绕不开的,今天就来依次聊聊他们
1.线程(thread)
多线程的意义在于一个应用程序中,有多个执行部分可以同时执行;对于比较耗时的操作(例如io,数据库操作),或者等待响应(如wcf通信)的操作,可以单独开启后台线程来执行,这样主线程就不会阻塞,可以继续往下执行;等到后台线程执行完毕,再通知主线程,然后做出对应操作!
在c#中开启新线程比较简单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static void main( string [] args)
{
console.writeline( "主线程开始" );
//isbackground=true,将其设置为后台线程
thread t = new thread(run) { isbackground = true };
t.start();
console.writeline( "主线程在做其他的事!" );
//主线程结束,后台线程会自动结束,不管有没有执行完成
//thread.sleep(300);
thread.sleep(1500);
console.writeline( "主线程结束" );
}
static void run()
{
thread.sleep(700);
console.writeline( "这是后台线程调用" );
}
|
执行结果如下图,
可以看到在启动后台线程之后,主线程继续往下执行了,并没有等到后台线程执行完之后。
1.1 线程池
试想一下,如果有大量的任务需要处理,例如网站后台对于http请求的处理,那是不是要对每一个请求创建一个后台线程呢?显然不合适,这会占用大量内存,而且频繁地创建的过程也会严重影响速度,那怎么办呢?线程池就是为了解决这一问题,把创建的线程存起来,形成一个线程池(里面有多个线程),当要处理任务时,若线程池中有空闲线程(前一个任务执行完成后,线程不会被回收,会被设置为空闲状态),则直接调用线程池中的线程执行(例asp.net处理机制中的application对象),
使用事例:
1
2
3
4
5
6
7
8
|
for ( int i = 0; i < 10; i++)
{
threadpool.queueuserworkitem(m =>
{
console.writeline(thread.currentthread.managedthreadid.tostring());
});
}
console.read();
|
运行结果:
可以看到,虽然执行了10次,但并没有创建10个线程。
1.2 信号量(semaphore)
semaphore负责协调线程,可以限制对某一资源访问的线程数量
这里对semaphoreslim类的用法做一个简单的事例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static semaphoreslim semlim = new semaphoreslim(3); //3表示最多只能有三个线程同时访问
static void main( string [] args)
{
for ( int i = 0; i < 10; i++)
{
new thread(semaphoretest).start();
}
console.read();
}
static void semaphoretest()
{
semlim.wait();
console.writeline( "线程" + thread.currentthread.managedthreadid.tostring() + "开始执行" );
thread.sleep(2000);
console.writeline( "线程" + thread.currentthread.managedthreadid.tostring() + "执行完毕" );
semlim.release();
}
|
执行结果如下:
可以看到,刚开始只有三个线程在执行,当一个线程执行完毕并释放之后,才会有新的线程来执行方法!
除了semaphoreslim类,还可以使用semaphore类,感觉更加灵活,感兴趣的话可以搜一下,这里就不做演示了!
2.task
task是.net4.0加入的,跟线程池threadpool的功能类似,用task开启新任务时,会从线程池中调用线程,而thread每次实例化都会创建一个新的线程。
1
2
3
4
5
6
7
8
9
10
11
|
console.writeline( "主线程启动" );
//task.run启动一个线程
//task启动的是后台线程,要在主线程中等待后台线程执行完毕,可以调用wait方法
//task task = task.factory.startnew(() => { thread.sleep(1500); console.writeline("task启动"); });
task task = task.run(() => {
thread.sleep(1500);
console.writeline( "task启动" );
});
thread.sleep(300);
task.wait();
console.writeline( "主线程结束" );
|
执行结果如下:
开启新任务的方法:task.run()或者task.factory.startnew(),开启的是后台线程
要在主线程中等待后台线程执行完毕,可以使用wait方法(会以同步的方式来执行)。不用wait则会以异步的方式来执行。
比较一下task和thread:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
static void main( string [] args)
{
for ( int i = 0; i < 5; i++)
{
new thread(run1).start();
}
for ( int i = 0; i < 5; i++)
{
task.run(() => { run2(); });
}
}
static void run1()
{
console.writeline( "thread id =" + thread.currentthread.managedthreadid);
}
static void run2()
{
console.writeline( "task调用的thread id =" + thread.currentthread.managedthreadid);
}
|
执行结果:
可以看出来,直接用thread会开启5个线程,用task(用了线程池)开启了3个!
2.1 task<tresult>
task<tresult>就是有返回值的task,tresult就是返回值类型。
1
2
3
4
5
6
7
8
9
|
console.writeline( "主线程开始" );
//返回值类型为string
task< string > task = task< string >.run(() => {
thread.sleep(2000);
return thread.currentthread.managedthreadid.tostring();
});
//会等到task执行完毕才会输出;
console.writeline(task.result);
console.writeline( "主线程结束" );
|
运行结果:
通过task.result可以取到返回值,若取值的时候,后台线程还没执行完,则会等待其执行完毕!
简单提一下:
task任务可以通过cancellationtokensource类来取消,感觉用得不多,用法比较简单,感兴趣的话可以搜一下!
3. async/await
async/await是c#5.0中推出的,先上用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
static void main( string [] args)
{
console.writeline( "-------主线程启动-------" );
task< int > task = getstrlengthasync();
console.writeline( "主线程继续执行" );
console.writeline( "task返回的值" + task.result);
console.writeline( "-------主线程结束-------" );
}
static async task< int > getstrlengthasync()
{
console.writeline( "getstrlengthasync方法开始执行" );
//此处返回的<string>中的字符串类型,而不是task<string>
string str = await getstring();
console.writeline( "getstrlengthasync方法执行结束" );
return str.length;
}
static task< string > getstring()
{
//console.writeline("getstring方法开始执行")
return task< string >.run(() =>
{
thread.sleep(2000);
return "getstring的返回值" ;
});
}
|
async用来修饰方法,表明这个方法是异步的,声明的方法的返回类型必须为:void,task或task<tresult>。
await必须用来修饰task或task<tresult>,而且只能出现在已经用async关键字修饰的异步方法中。通常情况下,async/await成对出现才有意义,
看看运行结果:
可以看出来,main函数调用getstrlengthasync方法后,在await之前,都是同步执行的,直到遇到await关键字,main函数才返回继续执行。
那么是否是在遇到await关键字的时候程序自动开启了一个后台线程去执行getstring方法呢?
现在把getstring方法中的那行注释加上,运行的结果是:
大家可以看到,在遇到await关键字后,没有继续执行getstrlengthasync方法后面的操作,也没有马上反回到main函数中,而是执行了getstring的第一行,以此可以判断await这里并没有开启新的线程去执行getstring方法,而是以同步的方式让getstring方法执行,等到执行到getstring方法中的task<string>.run()的时候才由task开启了后台线程!
那么await的作用是什么呢?
可以从字面上理解,上面提到task.wait可以让主线程等待后台线程执行完毕,await和wait类似,同样是等待,等待task<string>.run()开始的后台线程执行完毕,不同的是await不会阻塞主线程,只会让getstrlengthasync方法暂停执行。
那么await是怎么做到的呢?有没有开启新线程去等待?
只有两个线程(主线程和task开启的线程)!至于怎么做到的(我也不知道......>_<),大家有兴趣的话研究下吧!
4.iasyncresult
iasyncresult自.net1.1起就有了,包含可异步操作的方法的类需要实现它,task类就实现了该接口
在不借助于task的情况下怎么实现异步呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class program
{
static void main( string [] args)
{
console.writeline( "主程序开始--------------------" );
int threadid;
asyncdemo ad = new asyncdemo();
asyncmethodcaller caller = new asyncmethodcaller(ad.testmethod);
iasyncresult result = caller.begininvoke(3000, out threadid, null , null );
thread.sleep(0);
console.writeline( "主线程线程 {0} 正在运行." ,thread.currentthread.managedthreadid)
//会阻塞线程,直到后台线程执行完毕之后,才会往下执行
result.asyncwaithandle.waitone();
console.writeline( "主程序在做一些事情!!!" );
//获取异步执行的结果
string returnvalue = caller.endinvoke( out threadid, result);
//释放资源
result.asyncwaithandle.close();
console.writeline( "主程序结束--------------------" );
console.read();
}
}
public class asyncdemo
{
//供后台线程执行的方法
public string testmethod( int callduration, out int threadid)
{
console.writeline( "测试方法开始执行." );
thread.sleep(callduration);
threadid = thread.currentthread.managedthreadid;
return string .format( "测试方法执行的时间 {0}." , callduration.tostring());
}
}
public delegate string asyncmethodcaller( int callduration, out int threadid);
|
关键步骤就是红色字体的部分,运行结果:
和task的用法差异不是很大!result.asyncwaithandle.waitone()就类似task的wait。
5.parallel
最后说一下在循环中开启多线程的简单方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
stopwatch watch1 = new stopwatch();
watch1.start();
for ( int i = 1; i <= 10; i++)
{
console.write(i + "," );
thread.sleep(1000);
}
watch1.stop();
console.writeline(watch1.elapsed);
stopwatch watch2 = new stopwatch();
watch2.start();
//会调用线程池中的线程
parallel. for (1, 11, i =>
{
console.writeline(i + ",线程id:" + thread.currentthread.managedthreadid);
thread.sleep(1000);
});
watch2.stop();
console.writeline(watch2.elapsed);
|
运行结果:
循环list<t>:
1
2
3
4
5
6
|
list< int > list = new list< int >() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 };
parallel. foreach < int >(list, n =>
{
console.writeline(n);
thread.sleep(1000);
});
|
执行action[]数组里面的方法:
1
2
3
4
5
6
7
8
9
|
action[] actions = new action[] {
new action(()=>{
console.writeline( "方法1" );
}),
new action(()=>{
console.writeline( "方法2" );
})
};
parallel.invoke(actions);
|
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持服务器之家!
原文链接:http://www.cnblogs.com/doforfuture/p/6293926.html