根据我的理解,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;
}

当前回答

在更高的层次上:

1) Async关键字启用等待,这就是它所做的一切。Async关键字不会在单独的线程中运行该方法。beginf async方法同步运行,直到它命中一个耗时任务的await。

2)你可以等待一个返回Task或t类型Task的方法。你不能等待async void方法。

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

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

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

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

下面是示例代码。执行它并检查线程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();
        }

    }
}

其他回答

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

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

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

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

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

它们是为了让异步代码易于编写和阅读。

这和生成后台线程来执行长时间逻辑是一样的吗?

一点也不。

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

async关键字启用await关键字。所以任何使用await的方法都必须被标记为async。

// DoSomethingAsync()方法在5秒休眠后到达该行。难道不应该立即到达吗?

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

//是否在后台线程上执行?

No.


你可能会发现我的async/await介绍很有用。官方MSDN文档也非常好(尤其是TAP部分),异步团队还发布了一个很好的FAQ。

除了其他答案,还有await (c#参考)

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

下面的Windows窗体示例说明了await在 异步方法,WaitAsynchronouslyAsync。对比一下它的行为 方法使用waitsynchrontically的行为。没有等待 应用到任务的操作符,waitsynchronize同步运行 尽管在定义中使用了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";
}

这里所有的答案都使用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/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有技术理解的人。