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

我目前正在尝试最基本的例子。我内联添加了一些注释。你能给我解释一下吗?

// 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;
}

当前回答

解释

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

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

在下面的例子中,第一个块正是这样做的。它立即启动所有任务(Task。延迟线),并把它们放到一边。代码将在await一行上暂停,直到1秒的延迟完成,然后才进入下一行。由于b、c、d和e几乎与a同时开始执行(由于缺少await),因此在本例中它们应该大致同时完成。

在下面的例子中,第二个块正在启动一个任务,并在开始后续任务之前等待它完成(这就是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 PM
5/24/2017 2:22:51 PM (First block took 1 second)
5/24/2017 2:22:56 PM (Second block took 5 seconds)

关于SynchronizationContext的额外信息

注意:这就是我感到有点模糊的地方,所以如果我错了什么,请纠正我,我会更新答案。对它的工作原理有一个基本的了解是很重要的,但只要你从来没有使用过ConfigureAwait(false),你也可以成为这方面的专家,尽管我认为你可能会失去一些优化的机会。

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

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

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

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

await a.ConfigureAwait(false);

在程序暂停之后,它可能会继续在一个具有不同上下文的完全不同的线程上运行。这就是性能改进的来源——它可以在任何可用的线程上继续运行,而不必恢复它开始时的原始上下文。

这些东西让人困惑吗?地狱耶!你能算出来吗?可能!一旦你掌握了概念,然后转向Stephen Cleary的解释,它往往更适合那些已经对async/await有技术理解的人。

其他回答

我认为你用System.Threading.Thread.Sleep选了一个不好的例子

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

System.Threading.Thread.Sleep不是“正在完成”的事情,它只是休眠,因此你的下一行在5秒后到达……

阅读这篇文章,我认为它很好地解释了async和await概念:http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

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;
    }
}

这里所有的答案都使用Task.Delay()或其他内置的异步函数。但下面是我的例子,没有使用这些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;
}

我的理解是,还应该有第三个术语:任务。

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

Task是async函数的返回值。它是异步执行的。

您等待一个任务。当代码执行到这一行时,控制权跳回周围原始函数的调用者。

如果相反,你将一个异步函数(即任务)的返回值赋给一个变量,当代码执行到这一行时,它只是在任务异步执行时继续在周围的函数中越过这一行。

查看这个小提琴https://dotnetfiddle.net/VhZdLU(如果可能的话改进它),运行一个简单的控制台应用程序,在同一个程序中显示Task, Task. waitall (), async和await操作符的用法。

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

下面是示例代码

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 met       
        Console.WriteLine("Cursor Moved to Next Line Without Waiting for MyMethodAsync() completion");
        Console.WriteLine("Now Waiting for Task to be Finished");       
        Task.WaitAll(a); //Now Waiting      
        Console.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 here
        Console.WriteLine("Independent Works of now executes in MyMethodAsync()");
        //and now we call await on the task 
        int result = await longRunningTask;
        //use the result 
        Console.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 delay
        Console.WriteLine("LongRunningOperation() Finished after 2 Seconds");
        return 1;
    }   

}

来自输出窗口的跟踪: