65.9K
CodeProject 正在变化。 阅读更多。
Home

async / await 示例

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.62/5 (7投票s)

2014年7月14日

CPOL

2分钟阅读

viewsIcon

33129

downloadIcon

1196

一个简单的 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://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Three-Essential-Tips-For-Async-Introduction

http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx)

 

© . All rights reserved.