如何以及何时使用'async'和'wait'

根据我的理解,#0和#1所做的主要事情之一是使代码易于编写和阅读-但使用它们是否等于生成后台线程来执行长时间逻辑?

我现在正在尝试最基本的例子。我已经内联添加了一些注释。你能为我澄清一下吗?

// I don't understand why this method must be marked as `async`.private async void button1_Click(object sender, EventArgs e){Task<int> access = DoSomethingAsync();// task independent stuff here
// this line is reached after the 5 seconds sleep from// DoSomethingAsync() method. Shouldn't it be reached immediately?int a = 1;
// from my understanding the waiting should be done here.int x = await access;}
async Task<int> DoSomethingAsync(){// is this executed on a background thread?System.Threading.Thread.Sleep(5000);return 1;}
1162522 次浏览

我认为你选择了一个糟糕的例子System.Threading.Thread.Sleep

async任务的要点是让它在后台执行而不锁定主线程,例如执行DownloadFileAsync

System.Threading.Thread.Sleep不是“正在完成”的东西,它只是睡眠,因此您的下一行在5秒后到达。

读了这篇文章,我认为这是对asyncawait概念的一个很好的解释:http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

除了其他答案,看看等待(C#参考)

更具体地说,在例子中,它解释了你的情况

以下Windows窗体示例说明了在异步方法,WaitA同步异步。对比那个的行为方法的行为。没有等待运算符应用于任务,Wait同步同步运行尽管在其定义中使用了async修饰符并调用了

睡在它的身体里
private async void button1_Click(object sender, EventArgs e){// Call the method that runs asynchronously.string result = await WaitAsynchronouslyAsync();
// Call the method that runs synchronously.//string result = await WaitSynchronously ();
// Display the result.textBox1.Text += result;}
// The following method runs asynchronously. The UI thread is not// blocked during the delay. You can move or resize the Form1 window// while Task.Delay is running.public async Task<string> WaitAsynchronouslyAsync(){await Task.Delay(10000);return "Finished";}
// The following method runs synchronously, despite the use of async.// You cannot move or resize the Form1 window while Thread.Sleep// is running because the UI thread is blocked.public async Task<string> WaitSynchronously(){// Add a using directive for System.Threading.Thread.Sleep(10000);return "Finished";}

老实说,我仍然认为最好的解释是关于维基百科上的未来和承诺:http://en.wikipedia.org/wiki/Futures_and_promises

基本思想是,你有一个单独的线程池,可以异步执行任务。使用它时。然而,该对象确实承诺它将在某个时间执行操作,并在你请求它时给你结果。这意味着当你请求结果并且尚未完成时,它会阻塞,否则会在线程池中执行。

从那里你可以优化事情:一些操作可以异步实现,你可以通过将后续请求批处理在一起和/或重新排序来优化文件IO和网络通信等事情。我不确定这是否已经在Microsoft的任务框架中-但如果不是,那将是我要添加的第一件事。

实际上,你可以在C#4.0中实现未来的模式。如果你想确切地知道它是如何工作的,我可以推荐这个做得很好的链接:http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/。但是,如果你自己开始玩弄它,你会注意到,如果你想做所有很酷的事情,你真的需要语言支持——这正是微软所做的。

根据我的理解,async和wait做的主要事情之一是使代码易于编写和阅读。

它们使异步代码易于编写和阅读,是的。

执行长持续时间逻辑与生成后台线程是一回事吗?

一点也不。

//我不明白为什么这个方法必须标记为'async'。

async关键字启用await关键字。因此,任何使用await的方法都必须标记为async

//此行是在DoThingAsync()方法睡眠5秒后到达的。不是应该立即到达吗?

否,因为默认情况下async方法不会在另一个线程上运行。

//这是在后台线程上执行的吗?

不。


你可能会发现我的#0/#1介绍很有帮助。官方MSDN文档也非常好(特别是TAP部分),async团队推出了出色的FAQ

当使用asyncawait时,编译器在后台生成一个状态机。

这里有一个例子,我希望我能解释一些正在发生的高层细节:

public async Task MyMethodAsync(){Task<int> longRunningTask = LongRunningOperationAsync();// independent work which doesn't need the result of LongRunningOperationAsync can be done here
//and now we call await on the taskint result = await longRunningTask;//use the resultConsole.WriteLine(result);}
public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation{await Task.Delay(1000); // 1 second delayreturn 1;}

好的,那么这里发生了什么:

  1. Task<int> longRunningTask = LongRunningOperationAsync();开始执行LongRunningOperation

  2. 独立工作完成,假设主线程(线程ID=1)然后到达await longRunningTask

    现在,如果longRunningTask尚未完成并且仍在运行,MyMethodAsync()将返回其调用方法,因此主线程不会被阻塞。当longRunningTask完成时,ThreadPool中的线程(可以是任何线程)将返回到其先前上下文中的MyMethodAsync()并继续执行(在这种情况下将结果打印到控制台)。

第二种情况是longRunningTask已经完成执行并且结果可用。当到达await longRunningTask时,我们已经有了结果,因此代码将继续在同一个线程上执行。(在这种情况下,将结果打印到控制台)。当然,上面的例子不是这种情况,其中涉及Task.Delay(1000)

这个答案旨在提供一些特定于ASP.NET.的信息

通过在MVC控制器中使用async/wait,可以提高线程池利用率并实现更好的吞吐量,如下文所述,

http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4

在Web应用程序中,看到大量并发请求启动或有突发负载(并发突然增加),使这些Web服务调用异步将增加应用程序的响应能力。异步请求需要处理同步请求的时间相同。例如,如果请求进行Web服务调用需要两秒钟才能完成完成后,请求是否执行需要两秒钟同步或异步。但是,在异步调用期间,线程未被阻止响应其他请求,而它等待第一个请求完成。因此,异步请求防止请求排队和线程池增长许多并发请求调用长时间运行的操作。

这里有一个快速的控制台程序,可以让那些关注的人清楚。TaskToDo方法是你想要使其异步运行的长时间运行的方法。让它异步运行是由TestAsync方法完成的。测试循环方法只是运行TaskToDo任务并异步运行它们。你可以在结果中看到,因为它们从运行到运行的完成顺序不同——它们在完成时向控制台UI线程报告。简单,但我认为简单的例子比更多的例子更好地展示了模式的核心:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;
namespace TestingAsync{class Program{static void Main(string[] args){TestLoops();Console.Read();}
private static async void TestLoops(){for (int i = 0; i < 100; i++){await TestAsync(i);}}
private static Task TestAsync(int i){return Task.Run(() => TaskToDo(i));}
private async static void TaskToDo(int i){await Task.Delay(10);Console.WriteLine(i);}}}

在一个简单的控制台程序中显示上述解释:

class Program{static void Main(string[] args){TestAsyncAwaitMethods();Console.WriteLine("Press any key to exit...");Console.ReadLine();}
public async static void TestAsyncAwaitMethods(){await LongRunningMethod();}
public static async Task<int> LongRunningMethod(){Console.WriteLine("Starting Long Running method...");await Task.Delay(5000);Console.WriteLine("End Long Running method...");return 1;}}

输出是:

Starting Long Running method...Press any key to exit...End Long Running method...

因此,

  1. Main通过TestAsyncAwaitMethods启动长时间运行的方法。立即返回而不停止当前线程,我们立即看到“按任意键退出”消息
  2. 在此期间,LongRunningMethod在后台运行。完成后,Threadpool的另一个线程会获取此上下文并显示最终消息

因此,没有线程被阻塞。

正在使用它们等于生成背景线程来执行长时间时长逻辑?

本文MDSN:使用async和wait进行异步编程(C#)明确解释了它:

async和wait关键字不会导致额外的线程创建。异步方法不需要多线程,因为异步方法不会在自己的线程上运行。该方法在当前同步上下文并仅在线程上使用时间方法处于活动状态。

补充说明

这是一个高层次的async/await的快速示例。除此之外还有很多细节需要考虑。

注意:Task.Delay(1000)是模拟工作1秒,我们最好把它理解为等待外部资源的响应。由于我们的代码是在等待响应,因此系统可以将正在运行的任务设置为边上,完成后再返回。同时,它可以在该线程上完成一些其他工作。

在下面的示例中,第一个块正是这样做的。它立即启动所有任务(Task.Delay行)并将它们设置为侧面。代码将在await a行上暂停,直到1秒延迟完成后再转到下一行。由于bcde都在与a几乎完全相同的时间开始执行(由于缺乏等待),因此在这种情况下,它们应该在大致相同的时间完成。

在下面的示例中,第二块启动一个任务并等待它完成(这就是await所做的),然后再启动后续任务。每次迭代需要1秒。await暂停程序并等待结果,然后再继续。这是第一个块和第二个块之间的主要区别。

示例

Console.WriteLine(DateTime.Now);
// This block takes 1 second to run because all// 5 tasks are running simultaneously{var a = Task.Delay(1000);var b = Task.Delay(1000);var c = Task.Delay(1000);var d = Task.Delay(1000);var e = Task.Delay(1000);
await a;await b;await c;await d;await e;}
Console.WriteLine(DateTime.Now);
// This block takes 5 seconds to run because each "await"// pauses the code until the task finishes{await Task.Delay(1000);await Task.Delay(1000);await Task.Delay(1000);await Task.Delay(1000);await Task.Delay(1000);}Console.WriteLine(DateTime.Now);

输出:

5/24/2017 2:22:50 PM5/24/2017 2:22:51 PM (First block took 1 second)5/24/2017 2:22:56 PM (Second block took 5 seconds)

有关同步上下文的额外信息

注意:这是我有点模糊的地方,所以如果我在任何地方都错了,请纠正我,我会更新答案。对这是如何工作的有一个基本的了解是很重要的,但是只要你从不使用ConfigureAwait(false),你可以不成为专家,尽管你可能会失去一些优化的机会,我假设。

有一个方面使得async/await的概念有点难以理解。在这个例子中,这一切都发生在同一个线程上(或者至少它的SynchronizationContext看起来是同一个线程)。默认情况下,await将恢复它运行的原始线程的同步上下文。例如,在ASP.NET你有一个HttpContext,当请求进来时它绑定到一个线程。这个上下文包含特定于原始Http请求的东西,例如原始Request对象,该对象具有语言、IP地址、标头等内容。如果你在处理某些内容的中途切换线程,你可能最终试图在不同的HttpContext上从这个对象中提取信息,这可能是灾难性的。如果你知道你不会在任何事情上使用上下文,你可以选择“不关心”它。这基本上允许你的代码在单独的线程上运行,而不会带来上下文。

你如何实现这一点?默认情况下,await a;代码实际上是假设你确实想要捕获和恢复上下文:

await a; //Same as the line belowawait a.ConfigureAwait(true);

如果您想允许主代码在没有原始上下文的情况下在新线程上继续,您只需使用false而不是true,这样它就知道它不需要恢复上下文。

await a.ConfigureAwait(false);

程序完成后暂停,它将在具有不同上下文的完全不同的线程上继续潜在。这就是性能提高的来源-它可以在任何可用线程上继续,而无需恢复它开始时的原始上下文。

这些东西令人困惑吗?当然!你能弄清楚吗?可能吧!一旦你掌握了概念,然后继续斯蒂芬·克利里的解释,这些解释往往更适合已经对async/await有技术理解的人。

我理解它的方式也是,应该在混合中添加第三个术语:Task

Async只是你放在方法上的一个限定符,表示它是一个异步方法。

Taskasync函数的返回。它异步执行。

await一个任务。当代码执行到达此行时,控件跳转回周围原始函数的调用者。

如果相反,您将async函数(即Task)的返回值分配给一个变量,当代码执行到达这一行时,它只是继续越过周围函数中的该行,Task异步执行。

这里的所有答案都使用Task.Delay()或其他内置的async函数。但这是我的例子,不使用这些async函数:

// Starts counting to a large number and then immediately displays message "I'm counting...".// Then it waits for task to finish and displays "finished, press any key".static void asyncTest (){Console.WriteLine("Started asyncTest()");Task<long> task = asyncTest_count();Console.WriteLine("Started counting, please wait...");task.Wait(); // if you comment this line you will see that message "Finished counting" will be displayed before we actually finished counting.//Console.WriteLine("Finished counting to " + task.Result.ToString()); // using task.Result seems to also call task.Wait().Console.WriteLine("Finished counting.");Console.WriteLine("Press any key to exit program.");Console.ReadLine();}
static async Task<long> asyncTest_count(){long k = 0;Console.WriteLine("Started asyncTest_count()");await Task.Run(() =>{long countTo = 100000000;int prevPercentDone = -1;for (long i = 0; i <= countTo; i++){int percentDone = (int)(100 * (i / (double)countTo));if (percentDone != prevPercentDone){prevPercentDone = percentDone;Console.Write(percentDone.ToString() + "% ");}
k = i;}});Console.WriteLine("");Console.WriteLine("Finished asyncTest_count()");return k;}

请参阅此小提琴https://dotnetfiddle.net/VhZdLU(如果可能,请改进它)以运行简单的控制台应用程序,它显示了同一程序中任务,任务. WaitAll(),异步和等待运算符的用法。

这个小提琴应该清楚你的执行周期概念。

这是示例代码

using System;using System.Threading.Tasks;
public class Program{public static void Main(){var a = MyMethodAsync(); //Task started for Execution and immediately goes to Line 19 of the code. Cursor will come back as soon as await operator is metConsole.WriteLine("Cursor Moved to Next Line Without Waiting for MyMethodAsync() completion");Console.WriteLine("Now Waiting for Task to be Finished");Task.WaitAll(a); //Now WaitingConsole.WriteLine("Exiting CommandLine");}
public static async Task MyMethodAsync(){Task<int> longRunningTask = LongRunningOperation();// independent work which doesn't need the result of LongRunningOperationAsync can be done hereConsole.WriteLine("Independent Works of now executes in MyMethodAsync()");//and now we call await on the taskint result = await longRunningTask;//use the resultConsole.WriteLine("Result of LongRunningOperation() is " + result);}
public static async Task<int> LongRunningOperation() // assume we return an int from this long running operation{Console.WriteLine("LongRunningOperation() Started");await Task.Delay(2000); // 2 second delayConsole.WriteLine("LongRunningOperation() Finished after 2 Seconds");return 1;}
}

来自输出窗口的跟踪:输入图片描述

public static void Main(string[] args){string result = DownloadContentAsync().Result;Console.ReadKey();}
// You use the async keyword to mark a method for asynchronous operations.// The "async" modifier simply starts synchronously the current thread.// What it does is enable the method to be split into multiple pieces.// The boundaries of these pieces are marked with the await keyword.public static async Task<string> DownloadContentAsync()// By convention, the method name ends with "Async{using (HttpClient client = new HttpClient()){// When you use the await keyword, the compiler generates the code that checks if the asynchronous operation is finished.// If it is already finished, the method continues to run synchronously.// If not completed, the state machine will connect a continuation method that must be executed WHEN the Task is completed.

// Http request example.// (In this example I can set the milliseconds after "sleep=")String result = await client.GetStringAsync("http://httpstat.us/200?sleep=1000");
Console.WriteLine(result);
// After completing the result response, the state machine will continue to synchronously execute the other processes.

return result;}}

这里的答案可以作为有关wait/async的一般指导。它们还包含有关wait/async如何连接的一些细节。我想与您分享一些在使用此设计模式之前应该了解的实践经验。

术语“wait”是字面意思,所以你调用它的任何线程都将在继续之前等待方法的结果。在前台线程上,这是一个灾难。前台线程承担着构建你的应用程序的负担,包括视图、视图模型、初始动画以及你使用这些元素引导的任何其他内容。所以当你等待前台线程时,你是应用程序的停下。用户在似乎什么都没有发生时等待和等待。这提供了负面的用户体验。

您当然可以使用各种方法等待后台线程:

Device.BeginInvokeOnMainThread(async () => { await AnyAwaitableMethod(); });
// Notice that we do not await the following call,// as that would tie it to the foreground thread.try{Task.Run(async () => { await AnyAwaitableMethod(); });}catch{}

这些注释的完整代码位于https://github.com/marcusts/xamarin-forms-annoyances。请参阅名为AwaitAsyncAntipattern.sln.的解决方案

GitHub网站还提供了有关此主题的更详细讨论的链接。

下面是通过打开对话框读取excel文件的代码,然后使用异步等待运行异步从excel中逐行读取并绑定到网格的代码

namespace EmailBillingRates{public partial class Form1 : Form{public Form1(){InitializeComponent();lblProcessing.Text = "";}
private async void btnReadExcel_Click(object sender, EventArgs e){string filename = OpenFileDialog();
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();Microsoft.Office.Interop.Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(filename);Microsoft.Office.Interop.Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];Microsoft.Office.Interop.Excel.Range xlRange = xlWorksheet.UsedRange;try{Task<int> longRunningTask = BindGrid(xlRange);int result = await longRunningTask;
}catch (Exception ex){MessageBox.Show(ex.Message.ToString());}finally{//cleanup// GC.Collect();//GC.WaitForPendingFinalizers();
//rule of thumb for releasing com objects://  never use two dots, all COM objects must be referenced and released individually//  ex: [somthing].[something].[something] is bad
//release com objects to fully kill excel process from running in the backgroundMarshal.ReleaseComObject(xlRange);Marshal.ReleaseComObject(xlWorksheet);
//close and releasexlWorkbook.Close();Marshal.ReleaseComObject(xlWorkbook);
//quit and releasexlApp.Quit();Marshal.ReleaseComObject(xlApp);}
}
private void btnSendEmail_Click(object sender, EventArgs e){
}
private string OpenFileDialog(){string filename = "";OpenFileDialog fdlg = new OpenFileDialog();fdlg.Title = "Excel File Dialog";fdlg.InitialDirectory = @"c:\";fdlg.Filter = "All files (*.*)|*.*|All files (*.*)|*.*";fdlg.FilterIndex = 2;fdlg.RestoreDirectory = true;if (fdlg.ShowDialog() == DialogResult.OK){filename = fdlg.FileName;}return filename;}
private async Task<int> BindGrid(Microsoft.Office.Interop.Excel.Range xlRange){lblProcessing.Text = "Processing File.. Please wait";int rowCount = xlRange.Rows.Count;int colCount = xlRange.Columns.Count;
// dt.Column = colCount;dataGridView1.ColumnCount = colCount;dataGridView1.RowCount = rowCount;
for (int i = 1; i <= rowCount; i++){for (int j = 1; j <= colCount; j++){//write the value to the Gridif (xlRange.Cells[i, j] != null && xlRange.Cells[i, j].Value2 != null){await Task.Delay(1);dataGridView1.Rows[i - 1].Cells[j - 1].Value =  xlRange.Cells[i, j].Value2.ToString();}
}}lblProcessing.Text = "";return 0;}}
internal class async{}}

在更高的层次上:

1)Async关键字启用wait,这就是它所做的一切。Async关键字不会在单独的线程中运行该方法。开始的f async方法会同步运行,直到它在耗时的任务上遇到wait。

2)您可以等待返回T类型的任务或任务的方法。

3)主线程在耗时的任务上遇到等待或实际工作启动时,主线程返回当前方法的调用者。

4)如果主线程在仍在执行的任务上看到wait,它不会等待它并返回到当前方法的调用者。通过这种方式,应用程序保持响应。

5)等待处理任务,现在将在线程池中的单独线程上执行。

6)当这个等待任务完成时,它下面的所有代码都将由单独的线程执行

下面是示例代码。执行它并检查线程id

using System;using System.Threading;using System.Threading.Tasks;
namespace AsyncAwaitDemo{class Program{public static async void AsynchronousOperation(){Console.WriteLine("Inside AsynchronousOperation Before AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);//Task<int> _task = AsyncMethod();int count = await AsyncMethod();
Console.WriteLine("Inside AsynchronousOperation After AsyncMethod Before Await, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
//int count = await _task;
Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await Before DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
DependentMethod(count);
Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await After DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);}
public static async Task<int> AsyncMethod(){Console.WriteLine("Inside AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);int count = 0;
await Task.Run(() =>{Console.WriteLine("Executing a long running task which takes 10 seconds to complete, Thread Id: " + Thread.CurrentThread.ManagedThreadId);Thread.Sleep(20000);count = 10;});
Console.WriteLine("Completed AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
return count;}
public static void DependentMethod(int count){Console.WriteLine("Inside DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId + ". Total count is " + count);}
static void Main(string[] args){Console.WriteLine("Started Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
AsynchronousOperation();
Console.WriteLine("Completed Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
Console.ReadKey();}
}}

为了最快的学习…

  • 了解方法执行流程(带图表):3分钟

  • 问题反省(为了学习):1分钟

  • 快速通读语法糖:5分钟

  • 分享一个开发者的困惑:5分钟

  • 问题:快速将正常代码的实际实现更改为异步代码:2分钟

  • 下一步怎么办?

理解方法执行流程(带图表):3分钟

在此图像中,只需关注#6(仅此而已)输入图片描述

在#6步,执行耗尽了工作并停止了。要继续它需要来自getStringTeam(一种函数)的结果。因此,它使用await运算符暂停其进度并将控制权(产量)交还给调用者(我们所处的这个方法)。对getStringTeam的实际调用是在#2早些时候进行的。在#2时,做出了返回字符串结果的承诺。但是它什么时候会返回结果?我们是否应该(#1: AccessTheWebAsync)再次进行第二次调用?谁得到结果,#2(调用语句)还是#6(等待语句)

AccessTheWebAsync()的外部调用者现在也在等待。所以调用者在等待AccessTheWebAsync,AccessTheWebAsync目前正在等待GetStringAsync。有趣的是AccessTheWebAsync在等待之前做了一些工作(#4),也许是为了节省等待的时间。外部调用者(以及链中的所有调用者)也有同样的多任务自由,这是这个'async'的最大优势!你觉得它是同步的…或正常的,但它不是。

#2和#6是拆分的,所以我们有#4的优势(等待时工作)。但我们也可以在不拆分的情况下做到这一点。所以#2将是:string urlContents = await client.GetStringAsync("...");。这里我们看不到任何优势,但在链中的某个地方,一个函数将进行拆分,而其他函数则不进行拆分。这取决于你使用链中的哪个函数/类。这种从函数到函数的行为变化是这个主题中最令人困惑的部分

记住,方法已经返回(这里),它不能再返回(没有第二次)。那么调用者怎么知道呢?这都是关于任务!任务被返回的。等待任务状态(不是方法,不是值)。值将在任务中设置。任务状态将被设置为完成。调用者只是监视任务(#6)。所以6#是哪里/谁得到结果的答案。稍后进一步阅读这里

为了学习而反省问题:1分钟

让我们稍微调整一下问题:

如何以及何时使用 #0和#1 Tasks

因为学习Task会自动涵盖其他两个(并回答你的问题)。

整个想法很简单。一个方法可以返回任何数据类型(双精度、整型、对象等),但在这里我们只是否认这一点并强制一个Task对象返回!但是我们仍然需要返回的数据(无效除外),对吗?这将被设置在'Task'对象内部的标准属性中,例如:'Result'属性。

快速读完语法糖:5分钟

  • 原始非异步方法
internal static int Method(int arg0, int arg1){int result = arg0 + arg1;IO(); // Do some long running IO.return result;}
  • 一个全新的任务化方法来调用上述方法
internal static Task<int> MethodTask(int arg0, int arg1){Task<int> task = new Task<int>(() => Method(arg0, arg1));task.Start(); // Hot task (started task) should always be returned.return task;}

我们提到过wait或async吗?不。调用上述方法,您将获得一个可以监控的任务。您已经知道任务返回(或包含)什么…一个整数。

  • 调用任务有点棘手,这是关键字开始出现的时候。如果有一个方法调用原始方法(非异步),那么我们需要按照下面的方式编辑它。让我们调用方法任务()
internal static async Task<int> MethodAsync(int arg0, int arg1){int result = await HelperMethods.MethodTask(arg0, arg1);return result;}

上面的代码与下面的图像相同:输入图片描述

  1. 我们正在“等待”任务完成。因此await(强制语法)
  2. 由于我们使用wait,我们必须使用async(强制语法)
  3. Async为前缀的方法Async(编码标准)

await很容易理解,但剩下的两个(asyncAsync)可能不是:)。不过,它对编译器来说应该更有意义。

所以有两个部分。

  1. 创建“任务”(只有一个任务,它将是一个额外的方法)

  2. 创建语法糖以await+async调用任务(如果要转换非异步方法,这涉及更改现有代码)

请记住,我们有一个AccessTheWebAsync()的外部调用者,该调用者也不能幸免……即它也需要相同的await+async。并且链还在继续(因此这是一个可能影响许多类的破坏性更改)。它也可以被认为是一个非破坏性更改,因为原始方法仍然可以被调用。如果你想强加一个破坏性更改,请更改它的访问权限(或删除并将其移动到任务中),然后类将被迫使用任务方法。无论如何,在异步调用中,一端总是有一个Task,只有一个。

一切都好,但一位开发人员惊讶地看到Task

分享一个开发者的困惑:5分钟

一个开发人员犯了一个错误,没有实现Task,但它仍然有效!试着理解这个问题和公认的答案此处提供。希望你已经阅读并完全理解。总结是,我们可能不会看到/实现'任务',但它是在父类/关联类的某个地方实现的。同样地,在我们的例子中,调用已经构建的MethodAsync()比我们自己实现TaskMethodTask())要容易得多。大多数开发人员发现,在将代码转换为异步代码时,很难理解Tasks

提示:尝试找到一个现有的Async实现(如MethodAsyncToListAsync)来外包难度。所以我们只需要处理Async和wait(这很容易并且与普通代码非常相似)

问题:快速将正常代码的实际实现更改为异步操作:2分钟

数据层中的代码行开始中断(很多地方)。因为我们将一些代码从. Net Framework 4.2.*更新到. Net core。我们必须在1小时内修复整个应用程序!

var myContract = query.Where(c => c.ContractID == _contractID).First();

简单!

  1. 我们安装了EntityFramework nuget包,因为它具有Queryable扩展。或者换句话说,它执行Async实现(任务),因此我们可以在代码中使用简单的Asyncawait生存。
  2. 名称空间=Microsoft. EntityFrameworkCore

呼叫代码行被改变成这样

var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
  1. 方法签名更改为

Contract GetContract(int contractnumber)

async Task<Contract> GetContractAsync(int contractnumber)

  1. 调用方法也受到影响:GetContract(123456);被称为GetContractAsync(123456).Result;

等等!那是什么Result?好捕捉!GetContractAsync只返回Task而不是我们想要的值(Contract)。一旦操作的结果可用,它就会被存储起来,并在随后调用#0属性时立即返回。我们也可以用类似的“等待()”来实现超时

TimeSpan ts=TimeSpan. FromTimeSpan ts(150);//从毫秒开始计算时间间隔

如果有的话Console. WriteLine("经过的超时间隔");

  1. 我们在30分钟内到处都换了!

但是架构师告诉我们不要仅仅为了这个而使用EntityFramework库!哎呀!戏剧!然后我们做了一个自定义的任务实现(哎呀!)。你知道怎么做的。仍然很容易!…仍然哎呀…

下一步去哪里?有一个很棒的快速视频,我们可以看一下关于在ASP. Net Core中将同步调用转换为异步的,也许这是一个人读完这篇文章后的方向。或者我解释得够多了吗?;)

异步/等待

实际上,Async/Aetc是一对关键字,它们只是用于创建异步任务回调的语法糖。

以这个操作为例:

public static void DoSomeWork(){var task = Task.Run(() =>{// [RUNS ON WORKER THREAD]
// IS NOT bubbling up due to the different threadsthrow new Exception();Thread.Sleep(2000);
return "Hello";});
// This is the callbacktask.ContinueWith((t) => {// -> Exception is swallowed silentlyConsole.WriteLine("Completed");
// [RUNS ON WORKER THREAD]});}

上面的代码有几个缺点。错误不会传递,很难阅读。但是异步和等待进来帮助我们:

public async static void DoSomeWork(){var result = await Task.Run(() =>{// [RUNS ON WORKER THREAD]
// IS bubbling upthrow new Exception();Thread.Sleep(2000);
return "Hello";});
// every thing below is a callback// (including the calling methods)
Console.WriteLine("Completed");}

等待调用必须在异步方法中。这有一些优点:

  • 返回任务的结果
  • 自动创建回调
  • 检查错误并让它们在调用堆栈中冒泡(仅在调用堆栈中最多不等待调用)
  • 等待结果
  • 释放主线程
  • 在主线程上运行回调
  • 使用线程池中的工作线程执行任务
  • 使代码易于阅读
  • 和更多

:使用异步调用没有来执行这些操作。您必须使用任务库才能做到这一点,例如Task. Run()。

这是等待和无等待解决方案之间的比较

这是非异步解决方案:

public static long DoTask(){stopWatch.Reset();stopWatch.Start();
// [RUNS ON MAIN THREAD]var task = Task.Run(() => {Thread.Sleep(2000);// [RUNS ON WORKER THREAD]});// goes directly further// WITHOUT waiting until the task is finished
// [RUNS ON MAIN THREAD]
stopWatch.Stop();// 50 millisecondsreturn stopWatch.ElapsedMilliseconds;}

这是async方法:

public async static Task<long> DoAwaitTask(){stopWatch.Reset();stopWatch.Start();
// [RUNS ON MAIN THREAD]
await Task.Run(() => {Thread.Sleep(2000);// [RUNS ON WORKER THREAD]});// Waits until task is finished
// [RUNS ON MAIN THREAD]
stopWatch.Stop();// 2050 millisecondsreturn stopWatch.ElapsedMilliseconds;}

你实际上可以调用一个async方法而不使用wait关键字,但这意味着这里的任何异常都会在发布模式下被吞没:

public static Stopwatch stopWatch { get; } = new Stopwatch();
static void Main(string[] args){Console.WriteLine("DoAwaitTask: " + DoAwaitTask().Result + " ms");// 2050 (2000 more because of the await)Console.WriteLine("DoTask: " + DoTask() + " ms");// 50Console.ReadKey();}

异步和等待不适用于并行计算。它们用于不阻塞主线程。当涉及asp.net或Windows应用程序时,由于网络调用而阻塞主线程是一件坏事。如果您这样做,您的应用程序将无响应甚至崩溃。

查看msdocs以获取更多示例。

异步等待简单解释

简单类比

一个人可能会为他们的早班火车等待。这就是他们所做的一切,因为这是他们目前正在执行的主要任务。(同步编程(你通常做的!))

另一个人可能会在早上的火车上抽烟,然后喝咖啡。(异步编程)

什么是异步编程?

异步编程是程序员将选择在与执行主线程分开的线程上运行他的一些代码,然后在它完成时通知主线程。

async关键字实际上是做什么的?

将async关键字作为方法名的前缀,例如

async void DoSomething(){ . . .

允许程序员在调用异步任务时使用wait关键字。这就是它所做的一切。

为什么这很重要?

在许多软件系统中,主线程是为与用户界面特别相关的操作保留的。如果我在我的计算机上运行一个非常复杂的递归算法,需要5秒才能完成,但我在主线程(UI线程)上运行这个。当用户试图单击我的应用程序上的任何东西时,它将看起来被冻结,因为我的主线程已经排队,目前正在处理太多的操作。结果主线程无法处理鼠标单击以从按钮单击运行方法。

什么时候使用Async和等待?

当您做任何不涉及用户交互界面的事情时,最好使用异步关键字。

假设你正在编写一个程序,允许用户在手机上素描,但每隔5秒就会在互联网上查看天气。

我们应该等待每5秒一次的呼叫轮询呼叫网络以获取天气,因为应用程序的用户需要继续与移动触摸屏交互以绘制漂亮的图片。

如何使用异步和等待

根据上面的例子,这里有一些如何编写它的伪代码:

    //ASYNCHRONOUS//this is called using the await keyword every 5 seconds from a polling timer or something.
async Task CheckWeather(){var weather = await GetWeather();//do something with the weather now you have it}
async Task<WeatherResult> GetWeather(){
var weatherJson = await CallToNetworkAddressToGetWeather();return deserializeJson<weatherJson>(weatherJson);}
//SYNCHRONOUS//This method is called whenever the screen is pressedvoid ScreenPressed(){DrawSketchOnScreen();}

补充说明-更新

我忘了在我的原始笔记中提到,在C#中,您只能等待包装在任务中的方法。例如,您可以等待此方法:

// awaiting this will return a string.// calling this without await (synchronously) will result in a Task<string> object.async Task<string> FetchHelloWorld() {..

你不能等待不是这样的任务的方法:

async string FetchHelloWorld() {..

请随时查看任务类这里的源代码。

我想给我的两分钱,我很抱歉,如果任何其他答案包含我将解释的内容,我读了大部分,没有找到它,但我可能错过了一些东西。

我看到了很多误解和很多很好的解释,只是想解释async与并行编程的不同之处,我相信这会让事情更容易理解。

当您需要进行长时间的计算、处理器密集型工作时,如果可能的话,您应该选择使用并行编程来优化内核使用。这会打开一些线程并同时处理事情。

假设你有一个数字数组,并且想要对than中的每一个进行昂贵的长时间计算。并行是你的朋友。

异步编程用于不同的用例。

当您等待不依赖于处理器的东西时,它用于释放您的线程,例如IO(写入和读取磁盘),当您执行IO时,您的线程什么也不做,当您等待昂贵的查询从DB返回的结果时,也是如此。

当线程等待长时间返回结果时,异步方法会释放线程。此线程可以被应用程序的其他部分使用(例如,在Web应用程序中,它处理其他请求),或者可以返回操作系统以供其他用途。

当您的结果完成后,相同的线程(或另一个线程)将返回给您的应用程序以恢复处理。

异步编程在像. net这样的多线程环境中不是强制性的(但这是一个很好的做法),在Web应用程序中,其他线程将响应新请求,但如果你在像nodejs这样的单线程框架中,它是强制性的,因为你不能阻止你唯一的线程,否则你将无法响应任何其他请求。

总而言之,长处理器密集型计算将从并行编程中受益更多,而不依赖于您的处理器的长等待期(例如IO或DB查询或对某些API的调用)将从异步编程中受益更多。

这就是为什么Entity Framework有一个async api来保存、列出、查找等。

请记住,async/wait与等待或waitAll不同,上下文是不同的。Async/wait释放线程并且是异步编程。等待/waitAll阻止所有线程(它们没有被释放)以强制并行上下文中的同步…不同的东西…

希望这对某人有用…

async与函数一起使用,使其成为异步函数。wait关键字用于同步调用异步函数。wait关键字保持JS引擎执行,直到Promise被解析。

只有当我们想要立即得到结果时,我们才应该使用async&wait。也许函数返回的结果在下一行中被使用。

关注这个博客,这是非常好的写在简单的单词

最好的例子在这里,享受:输入图片描述

回答你的第二个问题-何时使用async-这是我们使用的一个相当简单的方法:

  • 运行时间超过50ms的长时间运行的I/O绑定任务-使用async
  • 长时间运行的CPU密集型任务-使用并行执行、线程等。

解释:当您进行I/O工作时-发送网络请求,从磁盘读取数据等-实际工作由“外部”硅(网卡,磁盘控制器等)完成。一旦工作完成-I/O设备驱动程序将“ping”操作系统,操作系统将执行您的继续代码,回调/等。

附言:50ms阈值是MS的建议。否则async增加的开销(创建状态机、执行上下文等)会吞噬所有的好处。现在找不到原始的MS文章,但这里也提到了https://www.red-gate.com/simple-talk/dotnet/net-framework/the-overhead-of-asyncawait-in-net-4-5/

async告诉编译器要特别对待一个函数,这个函数是可挂起/可恢复的,它以某种方式保存状态。await挂起一个函数,但也是一种执行纪律的方式,是限制性的;你需要指定你在等待的什么,你不能无缘无故地挂起,这使得代码更具可读性,也许也更有效率。这就打开了另一个问题。为什么不await多个东西,为什么一次只一个?我相信这是因为这样的模式建立了自己,程序员也在遵循最小惊讶原则。存在着模棱两可的可能性:你是满足于只满足其中一个条件,还是希望满足所有条件,也许只是其中的一些条件?