哦,这听起来很有趣。我现在还没有使用CTP,只是在审查白皮书。在看了Anders Hejlsberg的演讲之后,我认为我可以看出它如何能够证明其有用。
据我所知,async使编写异步调用更易于阅读和实现。就像现在编写迭代器一样容易(而不是手动编写功能)。这对于阻塞进程至关重要,因为在解除阻塞之前,不能执行任何有用的工作。如果您正在下载文件,则在获取该文件之前无法执行任何有用操作,浪费线程。考虑一下如何调用一个函数,您知道它将阻塞不确定的时间并返回一些结果,然后处理它(例如,在文件中存储结果)。你会怎么写呢?这里有一个简单的例子:
static object DoSomeBlockingOperation(object args)
{
Thread.Sleep(5 * 60 * 1000);
return args;
}
static void ProcessTheResult(object result)
{
Console.WriteLine(result);
}
static void CalculateAndProcess(object args)
{
object result = DoSomeBlockingOperation(args);
ProcessTheResult(result);
}
好的,我们已经实现了。但是等等,计算需要数分钟才能完成。如果我们想要一个交互式应用程序,并在计算进行时执行其他操作(如呈现UI),怎么办?这不好,因为我们同步调用函数,并且必须等待它完成,从而有效地冻结了应用程序,因为线程正在等待解除阻塞。
答案是,异步调用昂贵的函数。这样,我们不必等待阻塞操作完成。但是我们该如何做到这一点?我们会异步调用函数并注册回调函数,以便在解除阻塞时调用,以便我们可以处理结果。
static void CalculateAndProcessAsyncOld(object args)
;
calculate.BeginInvoke(args, cb, calculate);
}
- 注意:我们可以启动另一个线程来执行此操作,但这意味着我们要生成一个仅等待被解除阻塞的线程,然后执行一些有用的工作。这将是浪费。
现在调用是异步的,我们不必担心等待计算完成和处理,它是异步完成的。它会在能够完成时完成。与直接异步调用代码的替代方法是使用任务(Task):
static void CalculateAndProcessAsyncTask(object args)
{
Task<object> task = new Task<object>(DoSomeBlockingOperation, args);
task.ContinueWith(t =>
{
ProcessTheResult(t.Result);
});
task.Start();
}
现在我们已经异步调用了函数。但是,为了实现这一点,我们需要委托/任务能够异步调用它,需要一个回调函数来处理结果,然后再调用该函数。我们将一个两行的函数调用变成了更多的内容,只是为了异步调用某些东西。不仅如此,代码中的逻辑也比以前更复杂。虽然使用任务有助于简化过程,但我们仍然需要做一些工作才能实现它。我们只想异步运行,然后处理结果。为什么我们不能这样做呢?现在我们可以这样做:
// need to have an asynchronous version
static async Task<object> DoSomeBlockingOperationAsync(object args)
{
//it is my understanding that async will take this method and convert it to a task automatically
return DoSomeBlockingOperation(args);
}
static async void CalculateAndProcessAsyncNew(object args)
{
// let's calculate! (asynchronously)
object result = await DoSomeBlockingOperationAsync(args);
// let's process!
ProcessTheResult(result);
}
现在这只是一个简单的例子,包含简单的操作(计算、处理)。想象一下,如果每个操作不能方便地放入单独的函数中,而是有数百行代码。这会增加很多复杂性,仅为了获得异步调用的好处。
白皮书中提供了另一个实际的例子,即在UI应用程序中使用它。 修改为使用上面的示例:
private async void doCalculation_Click(object sender, RoutedEventArgs e) {
doCalculation.IsEnabled = false;
await DoSomeBlockingOperationAsync(GetArgs());
doCalculation.IsEnabled = true;
}
如果您做过任何UI编程(无论是WinForms还是WPF),并尝试在处理程序中调用昂贵的函数,您会知道这很方便。使用后台工作线程来完成此操作并不会有太大帮助,因为后台线程将一直等待,直到可以工作。
假设您有一种控制某些外部设备的方法,比如打印机。如果设备出现故障,您希望能够重新启动它。自然而然地,打印机需要一些时间才能启动并准备好进行操作。您可能需要考虑重新启动无效并尝试再次重新启动。您别无选择,只能等待它。但是,如果您使用异步方式进行操作,则不必等待。
static async void RestartPrinter()
{
Printer printer = GetPrinter();
do
{
printer.Restart();
printer = await printer.WaitUntilReadyAsync();
} while (printer.HasFailed);
}
想象一下不使用异步操作编写循环。
我还有一个例子。假设你需要在一个函数中执行多个阻塞操作,并希望异步调用。你会选择什么?
static void DoOperationsAsyncOld()
{
Task op1 = new Task(DoOperation1Async);
op1.ContinueWith(t1 =>
{
Task op2 = new Task(DoOperation2Async);
op2.ContinueWith(t2 =>
{
Task op3 = new Task(DoOperation3Async);
op3.ContinueWith(t3 =>
{
DoQuickOperation();
}
op3.Start();
}
op2.Start();
}
op1.Start();
}
static async void DoOperationsAsyncNew()
{
await DoOperation1Async();
await DoOperation2Async();
await DoOperation3Async();
DoQuickOperation();
}
阅读白皮书,它实际上有很多实用的例子,如编写并行任务等。
我迫不及待地想开始在CTP或.NET 5.0最终发布时使用它。