SemaphoreSlim.WaitAsync continuation code

匿名 (未验证) 提交于 2019-12-03 01:20:02

问题:

My understanding of the await keyword was that the code following the await qualified statement is running as the continuation of that statement once it is complete.

Hence the following two versions should produce the same output:

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

But they do not!

Here is the complete program:

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

Here is the output:

[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 ...

What am I missing?

回答1:

async-await optimizes for when the task you're awaiting on has already completed (which is the case when you have a semaphore set to 10 with only 1 thread using it). In that case the thread just carries on synchronously.

You can see that by adding an actual asynchronous operation to RunAsync and see how it changes the thread pool threads being used (which would be the behavior when your semaphore is empty and the caller actually needs to wait asynchronously):

public static async Task RunAsync(SemaphoreSlim sem) {     TraceThreadCount();     await Task.Delay(1000);     await sem.WaitAsync();     TraceThreadCount();     sem.Release(); }

You can also make this change to Run and have it execute the continuation synchronously and get the same results as in your RunAsync (thread count wise):

public static Task Run(SemaphoreSlim sem) {     TraceThreadCount();     return sem.WaitAsync().ContinueWith(t =>     {         TraceThreadCount();         sem.Release();     }, TaskContinuationOptions.ExecuteSynchronously); }

Output:

[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 ...  

Important Note: When it's said that async-await acts as a continuation it's more of an analogy. There are several critical difference between these concepts, especially regarding SynchronizationContexts. async-await automagically preserves the current context (unless you specify ConfigureAwait(false)) so you can use it safely in environments where that matters (UI, ASP.Net, etc.). More about synchronization contexts here.



回答2:

They won't as when you're calling async method, it's starting immediately. So, as long as your semaphore is not locked, WaitAsync() won't even start and there'll be no context switching (it's kind of optimization, same is applied to the canceled tasks), so your async method will be synchronous.

Meanwhile continuation version will actually start continuation on the parallel thread.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!