我知道这个问题可能有点微不足道,但我在互联网上找到的所有答案都让我感到困惑。
我已经了解await
如何工作的基本原则(如何SuperComplexMethod
异步等待任务完成而不阻塞主线程),
但是我不了解它的真正好处,因为在我看来你用async / await做的一切都可以使用Task Paralel Library。
请考虑这个例子,以便更好地理解我的意思:
让我们说我有internal class Program
{
private static void Main()
{
//I will start a task first that will run asynchroniously
var task = Task.Run(() => SuperComplexMethod());
//Then I will be doing some other work, and then get the result when I need it
Console.WriteLine("Doing some other work...");
var result = task.Result;
}
static string SuperComplexMethod()
{
Console.WriteLine("Doing very complex calculations...");
Thread.Sleep(3000);
return "Some result";
}
}
返回一些值,我想并行执行它,同时做一些其他事情。通常我会这样做:
async/await
在这里我将如何使用internal class Program
{
private static void Main()
{
var task = SuperComplexMethodAsync();
Console.WriteLine("Doing some other work...");
var result = task.Result;
}
//I have to create this async wrapper that can wait for the task to complete
async static Task<string> SuperComplexMethodAsync()
{
return await Task.Run(() => SuperComplexMethod());
}
static string SuperComplexMethod()
{
Console.WriteLine("Doing very complex calculations...");
Thread.Sleep(3000);
return "Some result";
}
}
:
async/await
正如您在第二个示例中看到的那样,为了使用async/await
方法,我必须创建一个启动任务并异步等待它完成的包装器方法。显然,对我来说这似乎是多余的,因为我可以在不使用标记为async/await
的此包装器的情况下实现相同的行为。
请您解释一下{{1}}有什么特别之处,以及它对单独使用任务并行库工具的实际好处是什么?
答案 0 :(得分:2)
可以说使用async / await的主要原因是线程保留。想象一下以下场景(我将简化以阐明这一点):a)您有一个Web应用程序,有10个线程可用于处理传入的请求; b)所有请求都涉及I / O(例如,连接到远程数据库,通过HTTP / SOAP连接到上游网络服务)来处理/完成; c)每个请求需要2秒钟才能处理。
现在想象20个请求大约在同一时间到达。如果没有async / await,您的Web应用程序将开始处理前10个请求。在发生这种情况时,其他10个人只会在队列中停留2秒钟,因为您的Web应用程序没有线程,因此无法处理它们。只有当前10个完成时才会开始处理第二个10。
在async / await下,前10个请求将开始执行任务,并且在等待这些任务时,处理它们的线程将返回到Web应用程序以处理其他请求。因此,您的网络应用程序将开始几乎直接处理第二个10,而不是等待。当前10个中等待的每个任务完成时,Web应用程序将继续处理其余的方法,或者在线程池线程或其中一个Web应用程序的线程(这取决于您如何配置事物)。我们通常可以在I / O场景中期望I / O是调用持续时间的大部分,因此我们可以合理地假设在上面的场景中,网络/数据库调用可能需要1.9秒和其余的代码(适应DTO,一些业务逻辑等)可能需要0.1秒。如果我们假设web应用程序线程处理了continuation(等待之后),那么该线程现在只被绑定了2秒中的0.1秒,而不是非async / await场景中的完整2秒。
你可能会自然而然地想:我只是将线程从一个线程池中推出,而另一个线程池中的线程也会被填满。要理解为什么在真正的异步场景中实际上并非如此,您需要阅读There Is No Thread。
结果是你现在能够同时处理比你有线程可用的更多请求。
你会注意到上面的内容主要集中在I / O上,而这正是async / await闪耀的地方。如果您的Web应用程序通过使用CPU执行复杂的数学计算来处理请求,您将看不到上述好处,因此async / await不适用于也不适用于CPU绑定活动。
在其他人加入规则的所有例外(并且有一些)之前,我只提出一个简单的vanilla场景来显示I / O绑定场景中async / await的值。覆盖有关async / await的所有内容将创建一个非常长的答案(这个已经足够长了!)
我还应该补充一点,还有其他方法可以异步处理Web请求,这些方法可以在async / await之前进行,但async / await可以非常简化实现。
-
简单地说一个WinForms或类似的应用程序,场景非常相似,除了现在你真的只有一个线程可用于处理UI请求,并且只要你抓住那个线程, UI将无响应,因此您可以使用类似的方法将远程运行的操作移出UI线程。在UI场景中,从UI线程执行CPU绑定操作也变得更加合理。执行此操作时,线程池线程将执行该CPU工作,释放UI线程以保持UI响应。现在是一个线程,但至少它不是UI。这通常称为“卸载”,这是async / await的其他主要用途之一。
-
您的示例是一个控制台应用程序 - 除了能够相当容易地(可以说比创建自己的线程更容易)在线程池上并发执行多个请求之外,在该上下文中通常不会获得很多。
答案 1 :(得分:0)
使用async并等待编译器在后台生成状态机
int main(){
char *ptr;
int N;
errno = 0;
if( argc > 2){
long NN = strtol(argv[1], &ptr, 10);
if (errno != 0 || *p != '\0' || NN > INT_MAX || NN < INT_MAX) {
fprintf(stderr, "%s\n", "Error in parsing");
exit(1);
}
else{
N = (int)NN;
}
}
int totalElmts = N*N;
if( N && totalElmts / N != N){
fprintf(stderr, "%s\n","Overflow");
}
if( argc != (totalElmts + 2) ){
fprintf(stderr, "%s %s %s\n",argv[0],"N","NxN elements");
}
for(size_t i = 2; argv[i] != NULL; i++)
{
//convert argv[i] to `int`
}
...
return 0;
}
任务longRunningTask = LongRunningOperationAsync();启动 执行LongRunningOperation
在我们假设主线程(线程ID)上完成独立工作 = 1)然后等待长时间运行任务。
现在,如果longRunningTask尚未完成且仍在运行,MyMethodAsync()将返回其调用方法,因此主线程不会被阻止。完成longRunningTask之后,ThreadPool中的一个线程(可以是任何线程)将在之前的上下文中返回MyMethodAsync()并继续执行(在这种情况下将结果打印到控制台)。
第二种情况是longRunningTask已经完成执行并且结果可用。当到达等待longRunningTask时,我们已经有了结果,所以代码将继续在同一个线程上执行。 (在这种情况下打印结果到控制台)。当然,上述示例并非如此,其中涉及Task.Delay(1000)。
更多...请参阅:=&gt;