提问



根据我的理解,asyncawait的主要功能之一是使代码易于编写和读取 - 但是使用它们等于生成后台线程以执行长持续时间逻辑?[66]]]


我目前正在尝试最基本的例子。我已经在内联添加了一些评论。你能为我澄清一下吗?


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

最佳参考


使用asyncawait时,编译器会在后台生成状态机。


这是一个例子,我希望我可以解释一些正在发生的高级细节:


public async Task MyMethodAsync()
{
    Task<int> longRunningTask = LongRunningOperationAsync();
    // independent work which doesn't need the result of LongRunningOperationAsync can be done here

    //and now we call await on the task 
    int result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation 
{
    await Task.Delay(1000); // 1 second delay
    return 1;
}


好的,所以这里发生了什么:



  1. Task<int> longRunningTask = LongRunningOperationAsync();开始执行LongRunningOperation

  2. 完成独立工作后,让我们假设主线程(线程ID=1),然后到达await longRunningTask


    现在,如果longRunningTask还没有完成并且它仍然在运行,MyMethodAsync()将返回其调用方法,因此主线程不会被阻塞。当longRunningTask完成后,来自ThreadPool的线程(可以是任何线程)将在其先前的上下文中返回MyMethodAsync()并继续执行(在这种情况下将结果打印到控制台)。



第二种情况是longRunningTask已经完成执行并且结果可用。到达await longRunningTask时,我们已经得到了结果,因此代码将继续在同一个线程上执行。 (在这种情况下打印结果到控制台)。当然,上述例子并非如此,其中涉及Task.Delay(1000)

其它参考1


继其他答案之后,看看等待(C#参考)[67]


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



  以下Windows窗体示例说明了在await中使用await
  异步方法,WaitAsynchronouslyAsync。对比那种行为
  具有WaitSynchronously行为的方法。没有等待
  运算符应用于任务,WaitSynchronously同步运行
  尽管在其定义中使用了异步修饰符并且调用了
  Thread.Sleep在它的身体里。



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

其它参考2



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



他们使异步代码易于编写和阅读,是的。



  是否与产生后台线程执行长持续时间逻辑相同?



一点也不。



  //我不明白为什么这个方法必须标记为异步。



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



  //在DoSomethingAsync()方法休眠5秒后达到此行。不应该马上到达吗?



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



  //这是在后台线程上执行的吗?



没有。





您可能会发现我的async/await介绍很有帮助。官方MSDN文档也非常好(特别是TAP部分),async团队提出了一个很好的FAQ。[68] [69] [70] [71]

其它参考3


说明



这是一个高级别的异步/等待的快速示例。除此之外还有更多细节需要考虑。


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


在下面的示例中,第一个块正是这样做的。它立即启动所有任务(Task.Delay行)并将它们放在一边。代码将在await a行上暂停,直到1秒延迟完成,然后再转到下一行。由于bcde都在a几乎完全相同的时间开始执行(由于缺乏等待),在这种情况下应该大致在同一时间完成。


在下面的示例中,第二个块在启动后续任务之前正在启动任务并等待它完成(这是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 program 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);


OUTPUT:


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


程序完成暂停后,它将在具有不同上下文的完全不同的线程上继续潜在。这是性能改进的来源 - 它可以继续在任何可用的线程上,而不必恢复它开始的原始上下文。


这个东西令人困惑吗?天啊,是的!你能搞清楚吗?大概!一旦你掌握了这些概念,就转向斯蒂芬克莱里的解释,这些解释往往更倾向于那些对async/await已有技术理解的人。

其它参考4


在简单的控制台程序中显示上述解释 -


class Program
{
    static void Main(string[] args)
    {
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    public async static void TestAsyncAwaitMethods()
    {
        await LongRunningMethod();
    }

    public static async Task<int> LongRunningMethod()
    {
        Console.WriteLine("Starting Long Running method...");
        await Task.Delay(5000);
        Console.WriteLine("End Long Running method...");
        return 1;
    }
}


输出是:


Starting Long Running method...
Press any key to exit...
End Long Running method...


从而,



  1. Main通过TestAsyncAwaitMethods启动长时间运行的方法。这会立即返回而不会暂停当前线程,我们会立即看到按任意键退出消息

  2. 所有这些,LongRunningMethod在后台运行。完成后,来自Threadpool的另一个线程将获取此上下文并显示最终消息



因此,不会阻止线程。

其它参考5


我认为你选择了System.Threading.Thread.Sleep的坏榜样


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


System.Threading.Thread.Sleep不是正在完成的东西,它只是睡觉,因此你的下一行在5秒后到达......


阅读这篇文章,我认为这是asyncawait概念的一个很好的解释:http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx [72]]]

其它参考6


这是一个快速的控制台程序,以便跟随的人清楚。 TaskToDo方法是您想要进行异步的长时间运行方法。使其运行Async由TestAsync方法完成。测试循环方法只运行TaskToDo任务并运行它们Async。您可以在结果中看到,因为它们不会在运行之间以相同的顺序完成 - 它们在完成时会向控制台UI线程报告。简单,但我认为简单的示例更好地展示了模式的核心比较多涉及的例子:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestingAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            TestLoops();
            Console.Read();
        }

        private static async void TestLoops()
        {
            for (int i = 0; i < 100; i++)
            {
                await TestAsync(i);
            }
        }

        private static Task TestAsync(int i)
        {
            return Task.Run(() => TaskToDo(i));
        }

        private async static void TaskToDo(int i)
        {
            await Task.Delay(10);
            Console.WriteLine(i);
        }
    }
}

其它参考7


这个答案旨在提供一些特定于ASP.NET的信息。


通过在MVC控制器中使用async/await,可以提高线程池利用率并实现更好的吞吐量,如下文所述,


http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4[73]



  在Web应用程序中看到大量并发请求
  启动或具有突发性负载(并发性突然增加),
  使这些Web服务调用异步将增加
  您的应用程序的响应性。异步请求需要
  处理同步请求的时间相同。例如,
  如果请求发出需要两秒钟的Web服务调用
  完成后,无论是否执行,请求都需要两秒钟
  同步或异步。但是,在异步调用期间,
  一个线程不会被阻止响应其他请求
  等待第一个请求完成。因此,异步
  请求会阻止请求排队和线程池增长
  许多并发请求调用长时间运行的操作。


其它参考8


说实话,我仍然认为最好的解释是关于维基百科的未来和承诺:http://en.wikipedia.org/wiki/Futures_and_promises [74]


基本思想是您有一个单独的线程池,可以异步执行任务。使用时。然而,该对象确实承诺它将在某个时间执行操作并在您请求时给出结果。这意味着它会在您请求结果时阻塞但尚未完成,但在线程池中执行。


从那里你可以优化一些事情:一些操作可以实现async,你可以通过将后续请求和/或重新排序它们进行批处理来优化文件IO和网络通信之类的事情。我不确定这是否已经存在于Microsoft的任务框架中 - 但如果不是这将是我要添加的第一件事。


实际上,您可以使用C#4.0中的产量实现未来的模式排序。如果你想知道它是如何工作的,我可以推荐这个做得不错的工作链接:http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/。但是,如果你自己开始玩它,你会注意到你真的需要语言支持,如果你想做所有很酷的事情 - 这正是微软所做的。[75]

其它参考9


这里的所有答案都使用Task.Delay()或其他一些内置的异步函数。但这是我的例子,不使用这些异步函数:


    // Starts counting to a large numbewr 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;
    }

其它参考10


请参阅此小提琴https://dotnetfiddle.net/VhZdLU(如果可能,请进行改进)以运行 简单控制台应用程序 ,其中显示任务,任务的用法。 WaitAll(),async和等待运算符在同一程序中。 [76]


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


这是示例代码


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

}


来自输出窗口的 跟踪:
[77]

其它参考11


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

其它参考12


我理解它的方式也是,应该在混合中加入第三个术语:Task


Async只是一个限定符,你在你的方法上说它是一个异步方法。


Taskasync函数的返回。它以异步方式执行。


await一个任务。当代码执行到达此行时,控件会跳回到周围原始函数的调用者。


相反,如果将async函数的返回值(即Task)赋给变量,当代码执行到达此行时,它只是继续超过周围的那一行函数而 Task异步执行。

其它参考13



  使用它们等于产生后台线程来执行很长时间
  持续逻辑?



本文MDSN:使用异步和等待的异步编程(C#)明确地解释了它:[78]



  async和await关键字不会导致额外的线程
  创建。异步方法不需要多线程,因为异步
  方法不会在自己的线程上运行。该方法在当前运行
  同步上下文并仅在线程上使用时间
  方法是活跃的。


其它参考14


在下面的代码中,HttpClient方法GetByteArrayAsync返回一个Task,getContentsTask。任务是在任务完成时生成实际字节数组的承诺。在getContentsTask完成之前,await运算符应用于getContentsTask以暂停SumPageSizesAsync中的执行。在此期间,控制权返回给SumPageSizesAsync的调用者。当getContentsTask完成时,await表达式求值为字节数组。


private async Task SumPageSizesAsync()
{
    // To use the HttpClient type in desktop apps, you must include a using directive and add a 
    // reference for the System.Net.Http namespace.
    HttpClient client = new HttpClient();
    // . . .
    Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
    byte[] urlContents = await getContentsTask;

    // Equivalently, now that you see how it works, you can write the same thing in a single line.
    //byte[] urlContents = await client.GetByteArrayAsync(url);
    // . . .
}

其它参考15


这里的答案可作为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的解决方案。[79]


GitHub站点还提供了有关此主题的更详细讨论的链接。

其它参考16


下面是通过打开对话框读取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
{
}


}
`