IT-Swarm.Net

如何以及何时使用'async'和'await'

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

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

// 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;
}
830
Dan Dinu

使用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)。 

629
Dan Dinu

进一步回答其他问题,看看 等待(C#参考)

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

以下Windows窗体示例说明了在异步方法WaitAsynchronouslyAsync中使用await。将方法的行为与WaitSynchronously的行为进行对比。没有await 运算符应用于任务,WaitSynchronously同步运行,尽管在其定义中使用了async修饰符并且在其正文中调用了 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";
}
142
Adriaan Stander

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

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

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

一点也不。

//我不明白为什么必须将此方法标记为“异步”。

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

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

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

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

没有。


您可以找到我的 async/await intro help。 官方MSDN文档 也非常好(特别是 _ tap _ section),而async团队推出了一个优秀的 _ faq _

122
Stephen Cleary

说明

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

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

在下面的示例中, 第一个块 正是这样做的。它立即启动所有任务(Task.Delay行)并将它们设置为侧面。代码将在await a行暂停,直到1秒延迟完成,然后再转到下一行。由于bcde几乎与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);

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

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

这个东西令人困惑吗?地狱啊!你能搞清楚吗?大概!一旦你掌握了这些概念,然后转向Stephen Cleary的解释,这些解释往往更倾向于对已经异步/等待有技术理解的人。

116
Joe Phillips

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

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的另一个线程将获取此上下文并显示最终消息

因此,不会阻止线程。 

47
sppc42

我认为你选择了System.Threading.Thread.Sleep的坏例子

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

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

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

38
Vnuk

这是一个快速的控制台程序,让跟踪者清楚。 “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);
        }
    }
}
17
MarkWalls

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

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

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

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

11
Lex Li

这里的所有答案都使用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
Tone Škoda

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

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

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

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

9
atlaste

看到这个小提琴 https://dotnetfiddle.net/VhZdLU (并在可能的情况下进行改进)运行简单控制台应用程序其中显示 任务, 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;
    }   

}

来自输出窗口的跟踪:enter image description here

6
vibs2006

为了最快的学习..

  • 理解方法执行流程(附图):3分钟

  • 问题内省(学习缘故):1分钟

  • 快速通过语法糖:5分钟

  • 分享开发人员的困惑:5分钟

  • 问题:快速将普通代码的实际实现更改为异步代码:2分钟

  • 下一步去哪儿?

理解方法执行流程(附图):3分钟

在这张图片中,只关注#6 enter image description here

在第6步:AccessTheWebAsync()已经停止工作,它可以在没有getStringTask结果的情况下完成。因此,AccessTheWebAsync使用await运算符来暂停其进度并将控制权返回(yield)给调用者。 AccessTheWebAsync将一个Task(字符串返回值)返回给调用者。该任务表示生成字符串结果的承诺。 但什么时候会再次拨打电话?再次拨打第二个电话?

AccessTheWebAsync()的调用者除了等待之外什么也没做(它可以完成一些内部任务,然后在需要时等待)。因此调用者正在等待AccessTheWebAsync,而AccessTheWebAsync正在等待GetStringAsync。

请记住,该方法已经返回,它无法再次返回(没有第二次)。那么来电者怎么知道呢?这都是关于 任务! 任务已返回。 等待任务 (不是方法,不是值)。值将在Task中设置。任务状态将设置为完成。呼叫者只监视任务。稍后进一步阅读 这里

为了学习而提出的问题反省:1分钟

让我们稍微调整一下问题: 

如何以及何时使用  asyncname__和awaitname__  Tasksname__?

因为学习Taskname__会自动覆盖其他2.为了学习至少。当然,这是关于asyncname__和awaitname__的问题的答案。

快速通过语法糖:5分钟

  • 转换前(原始方法)

    internal static int Method(int arg0, int arg1) { int result = arg0 + arg1; IO(); // Do some long running IO. return result; }

  • 另一个Task-ified方法来调用上面的方法

    internal static Task<int> MethodTask(int arg0, int arg1) { Task<int> task = new Task<int>(() => Method(arg0, arg1)); task.Start(); // Hot task (started task) should always be returned. return task; }

我们提到等待还是异步?不。调用上面的方法,你得到一个任务。哪个可以监控。你已经知道任务返回了什么..一个整数。

  • 调用任务有点棘手。我们调用MethodTask()

    internal static async Task<int> MethodAsync(int arg0, int arg1) { int result = await HelperMethods.MethodTask(arg0, arg1); return result; }

我们正在“等待”任务完成。因此awaitname__。由于我们使用await,我们必须使用async(强制)和MethodAsync,并使用'Async'作为前缀(编码标准)。稍后进一步阅读 这里

分享开发人员的困惑:5分钟

开发人员犯了一个错误,即没有实现Taskname__,但它仍然有效!尝试理解问题,只接受接受的答案 这里提供 。希望你已阅读并完全理解。同样在我们的示例中,调用已构建的MethodAsync()比使用Taskname __(MethodTask())自己实现该方法更容易。大多数开发人员发现在将代码转换为异步代码时很难理解Tasksname__。

提示:尝试查找现有的Async实现(如MethodAsyncname__或ToListAsyncname__)以外包难度。所以我们只需要处理Async和等待(这很简单,非常类似于普通代码)

问题:快速将普通代码的实际实现更改为异步操作:2分钟

数据层中显示的代码行开始中断(许多地方)。因为我们将一些代码从.Net framework 4.2更新为.Net核心。我们不得不在整个应用程序中1小时内解决这个问题! 

var myContract = query.Where(c => c.ContractID == _contractID).First();

十分简单!

  1. EntityFrameWork nuget(它有QueryableExtensions)
  2. namespace = Microsoft.EntityFrameworkCore 

代码改变了这样

var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
  1. 方法签名已更改

    Contract GetContract(int contractnumber) 

    至 

    async Task<Contract> GetContractAsync(int contractnumber)

  2. 调用方法也受到影响:GetContractAsync(123456);被调用为GetContractAsync(123456).Result;

  3. 我们在30分钟内到处改变了!

但是架构师告诉我们不要仅仅为此使用EntityFrameWork库!哎呀!戏剧!然后我们做了一个自定义的Task实现。你知道怎么做。还是很容易! 

下一步去哪儿? 有一个很棒的快速视频,我们可以看到 在ASP.Net核心中将同步调用转换为异步 ,因为这可能是读完这个之后的方向。 

4
Blue Clouds
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;
    }
}
3
Weslley Rufino de Lima

在更高的层次上:

1)Async关键字启用等待,这就是它的全部功能。 Async关键字不会在单独的线程中运行该方法。开始的f async方法同步运行,直到它等待一个耗时的任务。

2)您可以等待返回类型T的任务或任务的方法。您无法等待异步void方法。

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

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

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

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

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

    }
}
3
ABajpai

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

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

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

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

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

2
user21306

在下面的代码中,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);
    // . . .
}
1
lazydeveloper

异步/等待

实际上,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中等待调用)
  • 等待结果
  • 释放主线程
  • 在主线程上运行回调
  • 使用来自线程池的工作线程来完成任务
  • 使代码易于阅读
  • 还有更多

_ note _ :Async和Await用于with异步调用not来制作这些。你必须使用 Task Libary ,就像Task.Run()一样。

以下是await和none await解决方案之间的比较

这是无异步解决方案:

    public static long DoTask()
    {
        stopWatch.Reset();
        stopWatch.Start();

        // [RUNS ON MAIN THREAD]
        var task = Task.Run(() => {
            Thread.Sleep(2000);
            // [RUNS ON WORKER THREAD]
        });
        Thread.Sleep(1000);
        // goes directly further
        // WITHOUT waiting until the task is finished

        // [RUNS ON MAIN THREAD]

        stopWatch.Stop();
        // 50 milliseconds
        return stopWatch.ElapsedMilliseconds;
    }

这是异步方法:

    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关键字的情况下调用异步方法,但这意味着此处的任何异常都会在发布模式下被吞下:

    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应用程序。由于网络呼叫阻塞主线程是一件坏事。如果您这样做,您的应用将无法响应或可能会崩溃。

查看 ms docs 获取一些示例。

1
Hakim

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

本文 MDSN:使用异步和等待的异步编程(C#) 明确解释:

Async和await关键字不会导致创建的其他线程。异步方法不需要多线程,因为异步方法不能在自己的线程上运行。该方法在当前同步上下文上运行,并仅在方法处于活动状态时在线程上使用时间。

1
Dmitry G.

以下是我用来决定何时使用promises以及何时使用async等待的拇指规则

  1. 异步函数返回一个promise。反过来也是如此。每个返回promise的函数都可以被视为异步函数await用于调用异步函数并等待它解析或拒绝。

  2. await阻止在其所在的异步函数中执行代码。如果function2的输出依赖于function1的输出,那么我使用await。

  3. 如果两个函数可以并行运行,则创建两个不同的异步函数,然后并行运行它们。

  4. 要并行运行promises,创建一个promises数组,然后使用Promise.all(promisesArray)每次使用await时都要记住你正在编写阻塞代码。在一段时间内,我们往往忽视这一点。

  5. 而不是创建巨大的异步函数,其中有许多等待asyncFunction(),最好创建较小的异步函数。这样我们就会意识到没有编写过多的阻塞代码。使用较小的异步函数的另一个好处是,您可以强迫自己思考哪些是可以并行运行的异步函数。

  6. 如果您的代码包含阻塞代码,最好将其设置为异步功能。通过这样做,您确保其他人可以异步使用您的功能。

  7. 通过阻止代码生成异步函数,您将启用将调用您的函数的用户来决定他想要的异步性级别。

希望这有助于您轻松决定何时使用promises以及何时使用promises以及何时使用async-await

0
Sunny Sultan

下面是通过打开对话框读取Excel文件然后使用异步并等待运行异步代码的代码,该代码从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
    {
    }
}
0
Zaheer Abbas

异步和等待简单说明

简单类比

一个人可能他们的早班火车。这就是他们所做的一切,因为这是他们目前正在执行的主要任务。 (同步编程(你通常做的!))

另一个人可能 等待 他们早上的火车,他们抽烟然后喝咖啡。 (异步编程)

什么是异步编程?

异步编程是程序员选择在执行主线程的单独线程上运行他的一些代码然后在主线程完成时通知主线程的地方。

async关键字实际上做了什么?

将async关键字前缀为类似的方法名称 

async void DoSomething(){ . . .

允许程序员在调用异步任务时使用await关键字。就是这样。

为什么这很重要?

在许多软件系统中,主线程被保留用于与用户界面有关的操作。如果我正在运行一个非常复杂的递归算法,需要5秒才能在我的计算机上完成,但我在主线程(UI线程)上运行它当用户试图点击我的应用程序上的任何内容时,它似乎会被冻结因为我的主线程已排队,目前正在处理太多的操作。因此,主线程无法处理鼠标单击以从按钮单击运行该方法。

什么时候使用Async和Await?

当您执行不涉及用户界面的任何操作时,理想情况下使用异步关键字。 

因此,假设您正在编写一个允许用户在手机上绘制草图的程序,但是每隔5秒钟它就会检查互联网上的天气。

由于应用程序的用户需要与移动触摸屏保持交互以绘制漂亮的图片,我们应该等待轮询每隔5秒呼叫网络以获取天气。

你如何使用Async和Await

继上面的例子之后,这里有一些如何编写它的伪代码:

     //ASYNCHRONOUS
    //this is called every 5 seconds
    async void CheckWeather()
    {
        var weather = await GetWeather();
        //do something with the weather now you have it
    }

    async Task<WeatherResult> GetWeather()
    {

        var weatherJson = await CallToNetworkAddressToGetWeather();
        return deserializeJson<weatherJson>(weatherJson);
    }

    //SYNCHRONOUS
    //This method is called whenever the screen is pressed
    void ScreenPressed()
    {
        DrawSketchOnScreen();
    }
0
James Mallon

这里的答案可作为await/async的一般指导。它们还包含有关如何连接await/async的一些细节。在使用此设计模式之前,我想与您分享一些您应该了解的实践经验。

术语“await”是文字的,所以无论你调用什么线程,都会在继续之前等待方法的结果。在 foreground thread上,这是灾难。前台线程承担构建应用程序的负担,包括视图,视图模型,初始动画以及其他任何与这些元素一起引导的东西。所以当你等待前台线程时,你停止app。当没有任何事情发生时,用户等待并等待。这提供了负面的用户体验。

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

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

0
Stephen Marcus