新的C# await特性是什么?

83

有人能解释一下 await 函数是做什么的吗?


1
这是你所指的吗?C# 5.0中的异步编程第二部分:await从何而来? - jordanbtucker
1
请参阅使用Async和Await进行异步编程 - Robert Harvey
还有一些不错的例子可以在http://www.dotnetperls.com/async找到。 - Miljen Mikic
我不认为这个问题过于宽泛或应该关闭。它只是在问一个关键词的含义。(之前的版本有什么不同吗?) - Panzercrisis
5个回答

61

他们昨天在PDC上刚刚谈论过这个

Await与.NET中的任务(并行编程)一起使用。它是一个关键字,将在下一个版本的.NET中引入。它更或多或少地让您“暂停”方法的执行以等待任务完成执行。这里是一个简短的例子:

//create and run a new task  
Task<DataTable> dataTask = new Task<DataTable>(SomeCrazyDatabaseOperation);

//run some other code immediately after this task is started and running  
ShowLoaderControl();  
StartStoryboard();

//this will actually "pause" the code execution until the task completes.  It doesn't lock the thread, but rather waits for the result, similar to an async callback  
// please so also note, that the task needs to be started before it can be awaited. Otherwise it will never return
dataTask.Start();
DataTable table = await dataTask;

//Now we can perform operations on the Task result, as if we're executing code after the async operation completed  
listBoxControl.DataContext = table;  
StopStoryboard();  
HideLoaderControl();

12
听起来很像Thread.Join()。 - Steve Guidi
10
让我想起了COMEFROM - Joel Spolsky
20
为了完整起见,让我们补充一下上面的代码片段必须包含在一个带有async关键字修饰的方法中。该方法在第一个await关键字被遇到后立即返回。 - Przemek
14
用您的话说,它让您可以“暂停”方法,但需要注意的是,它并不会暂停或阻塞线程。 - Matt Wonlaw
2
这个答案是误导性的。await不会暂停任何东西,它只是在等待的Task仍在运行时,将方法的其余部分包装在继续任务中。 - Anri
显示剩余7条评论

47
基本上,asyncawait 关键字允许你指定方法在所有使用异步方法调用标记的await处停止执行,然后在异步操作完成时恢复执行。这使得你可以在应用程序的主线程中调用一个方法并异步处理复杂的工作,而无需显式定义线程和连接或阻塞应用程序的主线程。
可以将其类比于在生成IEnumerable的方法中使用yield return语句。当运行时遇到yield时,它将基本保存方法的当前状态,并返回被产生的值或引用。下次在返回对象(由运行时内部生成)上调用IEnumerator.MoveNext()时,方法的旧状态将还原到堆栈中,并按照yield return之后的下一行继续执行,就好像我们从未离开过该方法一样。如果没有此关键字,则必须自定义IEnumerator类型以存储状态并处理迭代请求,其中的方法可能变得非常复杂。
同样地,标记为async的方法必须至少有一个await。在await上,运行时会保存当前线程的状态和调用堆栈,进行异步调用,并展开回运行时的消息循环以处理下一条消息,并保持应用程序响应。当异步操作完成时,在下一个调度机会上,异步操作的调用堆栈被推回并继续执行,就像调用是同步的一样。
因此,这两个新关键字基本上简化了异步进程的编码,就像yield return简化了自定义可枚举对象的生成一样。使用几个关键字和一些背景知识,你可以跳过传统异步模式的混乱和经常容易出错的细节。这在任何事件驱动的GUI应用程序中都将非常重要,例如Winforms、WPF或Silverlight。

30
当前被接受的答案是误导性的。 await 不会暂停任何东西。 首先,它只能在标记为 async 并返回 Taskvoid 的方法或 lambda 中使用,如果你不介意在这个方法中运行 Task 实例,则可以返回 void
以下是示例:
internal class Program
{
    private static void Main(string[] args)
    {
        var task = DoWork();
        Console.WriteLine("Task status: " + task.Status);
        Console.WriteLine("Waiting for ENTER");
        Console.ReadLine();
    }

    private static async Task DoWork()
    {
        Console.WriteLine("Entered DoWork(). Sleeping 3");
        // imitating time consuming code
        // in a real-world app this should be inside task, 
        // so method returns fast
        Thread.Sleep(3000);

        await Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("async task iteration " + i);
                    // imitating time consuming code
                    Thread.Sleep(1000);
                }
            });

        Console.WriteLine("Exiting DoWork()");
    }
}

输出:

进入 DoWork()。休眠 3 秒
异步任务迭代 0
任务状态: 等待激活
等待输入回车键
异步任务迭代 1
异步任务迭代 2
异步任务迭代 3
异步任务迭代 4
异步任务迭代 5
异步任务迭代 6
异步任务迭代 7
异步任务迭代 8
异步任务迭代 9
退出 DoWork()


1
你也知道它会在给调用者任务之前阻塞3秒钟,这意味着如果从UI线程调用它,将会阻塞UI线程3秒钟。这种模式的目的是避免这样的操作。 - Servy
1
@Servy 是的,那就是重点。展示所有执行阶段。这不是一个真实世界的例子。 - Anri
7
@Servy 你在逗我吗? - Anri
2
不是的。我在努力帮助你改进你的回答。 - Servy
2
@ Anri...我非常感谢你在这里的努力。非常感谢!! - Praveen Prajapati
显示剩余8条评论

11

对于任何新接触 .NET 异步编程的人,这里有一个(完全虚构的)类比,使用您可能更熟悉的场景 - 使用 JavaScript/jQuery 进行 AJAX 调用。一个简单的 jQuery AJAX post 如下所示:

$.post(url, values, function(data) {
  // AJAX call completed, do something with returned data here
});

我们之所以在回调函数中处理结果,是因为我们在等待 AJAX 调用返回结果时不会阻塞当前线程。只有在响应准备好后,回调函数才会被触发,释放当前线程去做其他事情。

现在,如果 JavaScript 支持 await 关键字(当然它现在不支持(但还会支持!)),你可以使用以下代码实现同样的效果:

var data = await $.post(url, values);
// AJAX call completed, do something with returned data here

这看起来干净多了,但似乎我们引入了同步阻塞的代码。不过(虚假的)JavaScript编译器会将await之后的所有内容连接到回调函数中,在运行时第二个示例的行为就像第一个示例一样。

尽管看起来并没有省下太多工作,但当涉及到异常处理和同步上下文时,编译器实际上为您做了很多重活。如果想了解更多,请参考FAQsStephen Cleary的博客系列


坚持使用这个虚假的比喻(顺便说一句,这个比喻对我帮助很大,所以谢谢!),你所说的“everything after”是什么意思?是指在同一个函数(方法)范围内的所有内容吗?还是指任何可能添加到调用堆栈中的内容? - user1017882
2
“Everything after” = 方法的其余部分。编译器会将方法的其余部分有效地重写为回调函数,并立即将控制返回到当前方法的调用者。 - Todd Menier
1
太棒了,托德。再次感谢你的解释。我相信这对其他人也很有用。 - user1017882

-2
如果我必须在Java中实现它,它会看起来像这样:
/**
 * @author Ilya Gazman
 */
public abstract class SynchronizedTask{

    private ArrayList<Runnable> listeners = new ArrayList<Runnable>();

    private static final ThreadPoolExecutor threadPoolExecutor =  new ThreadPoolExecutor(6, 6, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1000));

    public final void await(Runnable listener){
        synchronized (this) {
            listeners.add(listener);
        }
    }

    public void excecute(){
        onExcecute();
        for (int i = listeners.size() - 1; i >= 0; i--) {
            Runnable runnable;
            synchronized (this) {
                runnable = listeners.remove(i);
            }
            threadPoolExecutor.execute(runnable);
        }
    }

    protected abstract void onExcecute();
}

你的应用程序将像这样使用它:

public class Test{
    private Job job = new Job();

    public Test() {
        craeteSomeJobToRunInBackground();
        methode1();
        methode2();
    }

    private void methode1(){
        System.out.println("Running methode 1");
        job.await(new Runnable() {

            @Override
            public void run() {
                System.out.println("Continue to running methode 1");
            }
        });
    }

    private void methode2(){
        System.out.println("Running methode 2");
    }

    private void craeteSomeJobToRunInBackground() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                job.excecute();
            }
        }).start();
    }

    private class Job extends SynchronizedTask{

        @Override
        protected void onExcecute() {
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Job is done");
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接