根据我的理解,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是一对关键字,它们只是用于创建异步任务回调的语法糖。

举个例子:

public static void DoSomeWork()
{
    var task = Task.Run(() =>
    {
        // [RUNS ON WORKER THREAD]

        // IS NOT bubbling up due to the different threads
        throw new Exception();
        Thread.Sleep(2000);

        return "Hello";
    });

    // This is the callback
    task.ContinueWith((t) => {
        // -> Exception is swallowed silently
        Console.WriteLine("Completed");

        // [RUNS ON WORKER THREAD]
    });
}

上面的代码有几个缺点。错误不会传递,而且很难阅读。 但是Async和Await来帮助我们:

public async static void DoSomeWork()
{
    var result = await Task.Run(() =>
    {
        // [RUNS ON WORKER THREAD]

        // IS bubbling up
        throw new Exception();
        Thread.Sleep(2000);

        return "Hello";
    });

    // every thing below is a callback 
    // (including the calling methods)

    Console.WriteLine("Completed");
}

Await调用必须在Async方法中。这有一些优点:

返回Task的结果 自动创建回调 检查错误并让它们在callstack中冒泡(只适用于callstack中的无等待调用) 等待结果 释放主线程 在主线程上运行回调 使用线程池中的工作线程执行任务 使代码易于阅读 还有更多

注意:Async和Await用于异步调用时不做这些。为此必须使用任务库,如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 milliseconds
    return 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 milliseconds
    return stopWatch.ElapsedMilliseconds;
}

实际上,你可以不使用await关键字而调用async方法,但这意味着这里的任何异常都会在释放模式下被吞噬:

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");
    // 50
    Console.ReadKey();
}

Async和Await并不用于并行计算。它们用于不阻塞主线程。当涉及asp.net或Windows应用程序时,由于网络调用阻塞主线程是一件糟糕的事情。如果你这样做,你的应用程序将得不到响应,甚至崩溃。

查看微软文档以获得更多的例子。

其他回答

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

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

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

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

下面是通过打开对话框读取excel文件的代码,然后使用async和等待运行异步代码,从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 background  
                Marshal.ReleaseComObject(xlRange);
                Marshal.ReleaseComObject(xlWorksheet);

                //close and release  
                xlWorkbook.Close();
                Marshal.ReleaseComObject(xlWorkbook);

                //quit and release  
                xlApp.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 Grid  
                    if (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
    {
    }
}

也许我的见解是相关的。Async告诉编译器要特别对待一个函数,这个函数是可挂起/可恢复的,它以某种方式保存状态。Await暂停了一个功能,但也是一种执行纪律的方式,是限制性的;你需要指定你在等待什么,你不能无故挂起,这使得代码更有可读性,也许也更有效率。这就引出了另一个问题。为什么不等待多件事,为什么一次只等待一件事?我相信这是因为这样的模式已经建立起来了,而程序员们遵循的是最小惊讶的原则。这里存在着模棱两可的可能性:您是满足其中一个条件,还是希望所有条件都得到满足,也许只是其中一些?

这里的答案可以作为await/async的一般指导。它们还包含一些关于await/async如何连接的细节。我想和大家分享一些在使用这个设计模式之前应该知道的实践经验。

术语“await”是字面意义上的,所以无论您在哪个线程上调用它,都将在继续之前等待该方法的结果。在前台线程上,这是一个灾难。前台线程承担了构建应用程序的负担,包括视图、视图模型、初始动画,以及其他任何与这些元素捆绑在一起的东西。所以当你等待前台线程时,你会停止应用程序。当什么都没有发生时,用户会一直等待。这提供了一种消极的用户体验。

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

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网站还提供了关于此主题的更详细讨论的链接。

解释

下面是一个高层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有技术理解的人。