我对await
关键字的理解是,await
限定语句后面的代码在完成后作为该语句的延续运行。
因此,以下两个版本应该产生相同的输出:
public static Task Run(SemaphoreSlim sem)
{
TraceThreadCount();
return sem.WaitAsync().ContinueWith(t =>
{
TraceThreadCount();
sem.Release();
});
}
public static async Task RunAsync(SemaphoreSlim sem)
{
TraceThreadCount();
await sem.WaitAsync();
TraceThreadCount();
sem.Release();
}
但他们没有!
这是完整的程序:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CDE
{
class Program
{
static void Main(string[] args)
{
try
{
var sem = new SemaphoreSlim(10);
var task = Run(sem);
Trace("About to wait for Run.");
task.Wait();
Trace("--------------------------------------------------");
task = RunAsync(sem);
Trace("About to wait for RunAsync.");
task.Wait();
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
Trace("Press any key ...");
Console.ReadKey();
}
public static Task Run(SemaphoreSlim sem)
{
TraceThreadCount();
return sem.WaitAsync().ContinueWith(t =>
{
TraceThreadCount();
sem.Release();
});
}
public static async Task RunAsync(SemaphoreSlim sem)
{
TraceThreadCount();
await sem.WaitAsync();
TraceThreadCount();
sem.Release();
}
private static void Trace(string fmt, params object[] args)
{
var str = string.Format(fmt, args);
Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, str);
}
private static void TraceThreadCount()
{
int workerThreads;
int completionPortThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
Trace("Available thread count: worker = {0}, completion port = {1}", workerThreads, completionPortThreads);
}
}
}
这是输出:
[9] Available thread count: worker = 1023, completion port = 1000
[9] About to wait for Run.
[6] Available thread count: worker = 1021, completion port = 1000
[9] --------------------------------------------------
[9] Available thread count: worker = 1023, completion port = 1000
[9] Available thread count: worker = 1023, completion port = 1000
[9] About to wait for RunAsync.
[9] Press any key ...
我错过了什么?
答案 0 :(得分:6)
async-await
优化您正在等待的任务何时已经完成(当您将信号量设置为10且只有1个线程使用它时)。在这种情况下,线程只是同步进行。
您可以通过向RunAsync
添加实际的异步操作来查看它是如何更改正在使用的线程池线程的(当您的信号量为空且调用者实际需要时,这将是行为等待异步):
public static async Task RunAsync(SemaphoreSlim sem)
{
TraceThreadCount();
await Task.Delay(1000);
await sem.WaitAsync();
TraceThreadCount();
sem.Release();
}
您也可以对Run
进行此更改,并让它同步执行延续,并获得与RunAsync
中相同的结果(线程计数):
public static Task Run(SemaphoreSlim sem)
{
TraceThreadCount();
return sem.WaitAsync().ContinueWith(t =>
{
TraceThreadCount();
sem.Release();
}, TaskContinuationOptions.ExecuteSynchronously);
}
输出:
[1] Available thread count: worker = 1023, completion port = 1000
[1] Available thread count: worker = 1023, completion port = 1000
[1] About to wait for Run.
[1] --------------------------------------------------
[1] Available thread count: worker = 1023, completion port = 1000
[1] Available thread count: worker = 1023, completion port = 1000
[1] About to wait for RunAsync.
[1] Press any key ...
重要提示:当它说async-await
作为延续时,它更像是一个类比。这些概念之间存在一些重要的区别,特别是关于SynchronizationContext
s。 async-await
自动保留当前上下文(除非您指定ConfigureAwait(false)
),以便您可以在重要的环境(UI,ASP.Net等)中安全地使用它。有关同步上下文的更多信息here。
答案 1 :(得分:0)
他们赢了,因为当您调用异步方法时,它立即启动。因此,只要您的信号量未被锁定,WaitAsync()
甚至无法启动,将无法进行上下文切换(它的优化,同样适用于已取消的任务),因此您的异步方法将是同步的。
同时,continuation版本实际上会在并行线程上开始延续。