async / await 示例






3.62/5 (7投票s)
一个简单的 async / await 示例
引言
本文展示了 async / await 调用及其输出,以便于理解。该示例的目的是调用两个函数,每个函数都包含 Task.Delay() 调用。本文将展示如何使用 async/Task 顺序执行这两个调用,同时保持应用程序的响应性。
关于演示应用程序
这是一个 C# 中的窗体应用程序。应用程序左侧有四个按钮。单击它们将执行与该按钮上提及的功能相关的代码。在按钮的右侧有一个文本框,其中显示执行的代码,然后有一个列表框,其中调用的函数打印文本行以演示流程。下面是 UI 的截图,供参考
注意:与本文相关的所有代码都可以在文件 AsyncDemoForm.cs 中找到
async void
async void 是一种“发射后即忘”机制。这非常适合高级别的事件处理程序。
在函数 CallAsyncVoidButton_Click() 中,对两个 async void 函数进行了调用:WriteRandomLine() 和 WriteLine()。
for (int i = 0; i < 10; i++) { WriteRandomLine(); WriteLine(); listBox.Items.Add("------------"); } listBox.Items.Add("ASYNC VOID BUTTON CLICK FINISHED");
运行示例时,可以观察到对函数 WriteRandomLine() 和 WriteLine() 的调用在命中这些函数内部的 await 时立即返回,因为 WriteRandomLine() 和 WriteLine() 是 async void 函数。一旦控制权返回到 CallAsyncVoidButton_Click() 函数,该函数中的执行将继续。WriteRandomLine() 和 WriteLine() 函数如下所示
private async void WriteRandomLine() { Random rand = new Random(); int rand_num = rand.Next(1200, 2000); // If the value in rand_num is less than 1500, then "Random Line" gets printed // otherwise "Non-Random Line" gets printed await Task.Delay(rand_num); listBox.Items.Add(string.Format("Random Line - delay = {0}ms, Thread Id = {1}", rand_num, Thread.CurrentThread.ManagedThreadId)); } private async void WriteLine() { await Task.Delay(1500); listBox.Items.Add("Non-Random Line - delay = 1500ms, Thread Id = " + Thread.CurrentThread.ManagedThreadId); }
async Task
在函数 CallAsyncTaskButton_Click() 中,对两个 async Task 函数进行了调用:WriteRandomLineAsync() 和 WriteLineAsync()。
当在 WriteRandomLineAsync() 内部命中 await 时,控制权返回到 for 循环,因为 WriteRandomLineAsync() 应用了 await 并且由于它返回一个 Task,控制权被赋予 CallAsyncTaskButton_Click() 的调用者。在 WriteRandomLineAsync() 处的 await 完成之前,CallAsyncTaskButton_Click() 中的执行不会进一步进行。一旦 WriteRandomLineAsync() 完成,WriteLineAsync() 就会被调用。这里的流程与 WriteRandomLineAsync() 类似,因为 await 以相同的方式应用。
private async void CallAsyncTaskButton_Click(object sender, EventArgs e) { // clear the list before writing listBox.Items.Clear(); listBox.Items.Add(string.Format("Current Thread Id: {0}", Thread.CurrentThread.ManagedThreadId)); for (int i = 0; i < 10; i++) { // comment the await in below two lines and it would behave the same way as // when async void button Click() gets called await WriteRandomLineAsync(); await WriteLineAsync(); listBox.Items.Add("------------"); } listBox.Items.Add("ASYNC BUTTON CLICK FINISHED"); } private async Task WriteRandomLineAsync() { Random rand = new Random(); int rand_num = rand.Next(1200, 2000); // If the value in rand_num is less than 1500, then "Random Line" gets printed // otherwise "Non-Random Line" gets printed await Task.Delay(rand_num); listBox.Items.Add(string.Format("Random Line - delay = {0}ms, Thread Id = {1}", rand_num, Thread.CurrentThread.ManagedThreadId)); } private async Task WriteLineAsync() { await Task.Delay(1500); listBox.Items.Add("Non-Random Line - delay = 1500ms, Thread Id = " + Thread.CurrentThread.ManagedThreadId); }
在这种情况下,结果会按照调用的顺序打印到列表框中,同时 UI 保持响应。
单击“同步调用”按钮以了解差异,在这种情况下,UI 在所有函数调用完成之前将不会响应。
同步调用
这是正常的同步调用,已添加是为了演示 UI 变得无响应的方式。当此代码执行时,尝试移动窗口周围不会像预期的那样响应。
private void SynchronousCallButton_Click(object sender, EventArgs e) { // clear the list before writing listBox.Items.Clear(); for (int i = 0; i < 10; i++) { SynchronousWait10ms(); SynchronousWait20ms(); listBox.Items.Add("------------"); } } private void SynchronousWait10ms() { Thread.Sleep(200); listBox.Items.Add("Wait 10 ms, Thread Id = " + Thread.CurrentThread.ManagedThreadId); } private void SynchronousWait20ms() { Thread.Sleep(300); listBox.Items.Add("Wait 20 ms, Thread Id = " + Thread.CurrentThread.ManagedThreadId); }
Task.Start
这里调用 Task.Start 来启动将在不同线程上运行的任务。该线程将从线程池中分配。在这里,UI 也是响应的,但它需要为非 CPU 密集型代码提供昂贵的线程资源。
private void TaskStartButton_Click(object sender, EventArgs e) { // clear the list before writing listBox.Items.Clear(); Task demoTask = new Task(DemoTask); demoTask.Start(); EnableAllButtons(); } private void DemoTask() { try { List<Task> tsk1 = new List<Task> (); List<Task> tsk2 = new List<Task> (); for (int i = 0; i < 10; i++) { tsk1.Add(new Task(TaskDemoWait100ms)); tsk2.Add(new Task(TaskDemoWaitWait200ms)); } for (int i = 0; i < 10; i++) { tsk1[i].Start(); tsk2[i].Start(); tsk2[i].Wait(); listBox.BeginInvoke(new separatorDelegate(() => { listBox.Items.Add("------------"); })); } } catch (Exception ex) { string msg = ex.Message; } } delegate void taskdelegate(int threadId); delegate void separatorDelegate(); private void TaskDemoWait100ms() { Thread.Sleep(100); object[] param = new object[1]; param[0] = (object)Thread.CurrentThread.ManagedThreadId; listBox.BeginInvoke(new taskdelegate((int threadId) => { listBox.Items.Add("Wait 100 ms, Thread Id = " + threadId); }), param); } private void TaskDemoWaitWait200ms() { Thread.Sleep(200); object[] param = new object[1]; param[0] = (object)Thread.CurrentThread.ManagedThreadId; listBox.BeginInvoke(new taskdelegate((int threadId) => { listBox.Items.Add("Wait 200 ms, Thread Id = " + threadId); }), param); }
环境
在 Visual Studio 2013 中开发,并在 Windows 8.1 with update 2 上进行了测试。
一些有趣的链接
https://codeproject.org.cn/Articles/127291/C-vNext-New-Asynchronous-Pattern
http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx)