SemaphoreSlim.WaitAnc延续代码

本文关键字:代码 延续 WaitAnc SemaphoreSlim | 更新日期: 2023-09-27 18:20:43

我对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 ...

我错过了什么?

SemaphoreSlim.WaitAnc延续代码

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等)中安全地使用它。有关同步上下文的更多信息,请点击此处。

此外,await Task.Delay(1000);可以用await Task.Yield();代替,以说明定时是不相关的,并且仅仅是该方法异步等待的事实是重要的。Task.Yield()在异步代码的单元测试中通常很有用。

它们不会像您调用异步方法时那样,它会立即启动。因此,只要你的信号量没有被锁定,WaitAsync()甚至不会启动,就不会有上下文切换(这是一种优化,同样适用于被取消的任务),所以你的异步方法将是同步的

同时,continuation版本实际上会在并行线程上启动continuation。