一个
Task
对象代表了一个未完成操作的延迟结果。如果你没有任何未完成操作,那么你不需要使用任务和
async/await
。否则,我相信
async
/
await
代码通常比其裸的TPL
ContinueWith
方法更高效。
现在我们来测试一下时间:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
static async Task<int> Test1Async(Task<int> task)
{
return await task;
}
static Task<int> Test2Async(Task<int> task)
{
return task.ContinueWith(
t => t.Result,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
static void Tester(string name, Func<Task<int>, Task<int>> func)
{
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < 10000000; i++)
{
func(Task.FromResult(0)).Wait();
}
sw.Stop();
Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}
static void Main(string[] args)
{
Tester("Test1Async", Test1Async);
Tester("Test2Async", Test2Async);
}
}
}
输出结果:
Test1Async: 1582毫秒
Test2Async: 4975毫秒
因此,默认情况下,使用
await
关键字的延续比
ContinueWith
延续处理效率更高。让我们稍微优化一下这段代码:
static async Task<int> Test1Async(Task<int> task)
{
if (task.IsCompleted)
return task.Result;
return await task;
}
static Task<int> Test2Async(Task<int> task)
{
if (task.IsCompleted)
return Task.FromResult(task.Result);
return task.ContinueWith(
t => t.Result,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
输出结果:
Test1Async:1557毫秒
Test2Async:429毫秒
现在非异步版本获胜。在使用async版本时,我相信这种优化已经在内部通过async/await基础设施完成了。
无论如何,到目前为止我们只处理了已完成的任务(Task.FromResult)。让我们引入实际的异步性(自然地,这次我们将做更少的迭代):
static Task<int> DoAsync()
{
var tcs = new TaskCompletionSource<int>();
ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(0));
return tcs.Task;
}
static void Tester(string name, Func<Task<int>, Task<int>> func)
{
ThreadPool.SetMinThreads(200, 200);
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
func(DoAsync()).Wait();
}
sw.Stop();
Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}
输出结果:
Test1Async: 4207毫秒
Test2Async: 4734毫秒
现在两者的差距非常小,虽然async
版本仍然稍微更快一些。但我认为这样的收益是微不足道的,与异步操作的实际成本或恢复捕获上下文的成本相当。
总之,如果你处理异步任务,出于易用性、可读性和可维护性的考虑,请选择async
/await
,而不是出于性能原因。
Async
和await
可能是最好的选择。 - mason