C#提供了多种执行异步执行的方法,如线程,期货和异步。
在什么情况下异步是最佳选择?
我已经阅读过很多关于如何和 async的文章,但到目前为止我还没有看过任何讨论为什么的文章
最初我认为async是一种创建未来的内置机制。像
这样的东西async int foo(){ return ..complex operation..; }
var x = await foo();
do_something_else();
bar(x);
致电'等待foo'将立即返回,并使用' x'会等待' foo'的返回值。 async不会这样做。如果您需要此行为,可以使用期货库:https://msdn.microsoft.com/en-us/library/Ff963556.aspx
上面的示例将类似于
int foo(){ return ..complex operation..; }
var x = Task.Factory.StartNew<int>(() => foo());
do_something_else();
bar(x.Result);
这并不像我希望的那样漂亮,但它确实有效。
因此,如果您有一个问题,您希望让多个线程对该工作进行操作,那么请使用future或其中一个并行操作,例如Parallel.For。
那么,async / await可能不适用于并行执行工作以提高吞吐量的用例。
答案 0 :(得分:9)
async解决了在创建多个线程时,为大量异步事件(如I / O)扩展应用程序的问题。
想象一个Web服务器,请求在进入时立即处理。处理发生在每个函数调用是同步的单个线程上。完全处理线程可能需要几秒钟,这意味着在处理完成之前消耗整个线程。
服务器编程的一种天真的方法是为每个请求生成一个新线程。通过这种方式,每个线程完成所需的时间并不重要,因为没有线程会阻塞任何其他线程。这种方法的问题在于线程并不便宜。底层操作系统只能在耗尽内存或其他类型的资源之前创建这么多线程。每个请求使用1个线程的Web服务器可能无法每秒超过几百/千个请求。 c10k挑战要求现代服务器能够扩展到10,000个并发用户。 http://www.kegel.com/c10k.html
更好的方法是使用线程池,其中存在的线程数或多或少是固定的(或者至少不会扩展超过一些可容忍的最大值)。在该场景中,只有固定数量的线程可用于处理传入请求。如果有多个请求而不是可用于处理的线程,则某些请求必须等待。如果一个线程正在处理一个请求并且必须等待一个长时间运行的I / O进程,那么这个线程就没有被最大限度地利用,并且服务器的吞吐量将远远低于其他情况。
现在的问题是,我们如何拥有固定数量的线程但仍能有效地使用它们?一个答案是“切断”程序逻辑,以便当一个线程正常等待I / O进程时,它将启动I / O进程,但是对于任何其他想要执行的任务,它立即变得空闲。在I / O之后执行的程序部分将存储在 thing 中,该 thing 知道如何继续执行。
例如,原始同步代码可能看起来像
void process(){
string name = get_user_name();
string address = look_up_address(name);
string tax_forms = find_tax_form(address);
render_tax_form(name, address, tax_forms);
}
look_up_address 和 find_tax_form 必须与数据库通信和/或向其他网站发出请求。
异步版本可能看起来像
void process(){
string name = get_user_name();
invoke_after(() => look_up_address(name), (address) => {
invoke_after(() => find_tax_form(address), (tax_forms) => {
render_tax_form(name, address, tax_forms);
}
}
}
这是继续传递样式,其中接下来要做的事情作为第二个lambda传递给一个函数,该函数在调用阻塞操作(在第一个lambda中)时不会阻塞当前线程。这很有效,但它很快变得非常难看并且难以遵循程序逻辑。
程序员在分割程序时手动完成的操作可以通过async / await自动完成。每次调用I / O函数时,程序都可以用 await 标记该函数调用,以通知调用者程序它可以继续执行其他操作而不是等待。
async void process(){
string name = get_user_name();
string address = await look_up_address(name);
string tax_forms = await find_tax_form(address);
render_tax_form(name, address, tax_forms);
}
执行进程的线程在到达 look_up_address 时会中断该函数并继续执行其他工作:例如处理其他请求。当 look_up_address 已经完成并且进程准备好继续时,某个线程(或同一个线程)将在最后一个线程停止的地方拾取并执行下一行 find_tax_forms(地址)
由于我目前对异步的看法是关于管理线程,我不相信异步对UI编程很有意义。通常,UI不会有那么多需要处理的同时事件。与UI异步的用例是阻止UI线程被阻止。尽管异步可以与UI一起使用,但我发现它很危险,因为在一些长时间运行的函数中省略等待,由于意外或遗忘,会导致UI阻塞。
async void button_callback(){
await do_something_long();
....
}
此代码不会阻止UI,因为它使用等待它调用的长时间运行的函数。如果稍后再添加另一个函数调用
async void button_callback(){
do_another_thing();
await do_something_long();
...
}
如果程序员添加了对 do_another_thing 的调用只是需要执行多长时间,那么现在将阻止UI。总是在后台线程中执行所有处理似乎更安全。
void button_callback(){
new Thread(){
do_another_thing();
do_something_long();
....
}.start();
}
现在不可能阻止UI线程,并且创建太多线程的可能性非常小。