C# 在简单的自定义同步上下文上使用async、await的线程峰值数

C# 在简单的自定义同步上下文上使用async、await的线程峰值数,c#,.net,multithreading,task-parallel-library,async-await,C#,.net,Multithreading,Task Parallel Library,Async Await,我编写了一个简单的同步上下文,它什么也不做,只是将要在同一线程上执行的任务排队,一次一个 但是,当它遇到等待时,似乎会触发一个新的线程。 线程数增加了一倍。我很难理解发生了什么。这不应该只运行3个线程吗(1个线程用于应用程序,另外2个线程用于GC、JIT) 但它立即飙升至6 更新:在x64发行版上运行,在具有2核-4线程的Intel Core i5上运行。 Microsoft.NET 4.5 SDK,已启用优化 using System; using System.Collections.Gen

我编写了一个简单的同步上下文,它什么也不做,只是将要在同一线程上执行的任务排队,一次一个

但是,当它遇到等待时,似乎会触发一个新的线程。 线程数增加了一倍。我很难理解发生了什么。这不应该只运行3个线程吗(1个线程用于应用程序,另外2个线程用于GC、JIT)

但它立即飙升至6

更新:在x64发行版上运行,在具有2核-4线程的Intel Core i5上运行。 Microsoft.NET 4.5 SDK,已启用优化

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    using System.Diagnostics;
    using System.Threading;

    class Program
    {
        static void Main(string[] args)
        {
            var ctx = new SyncContext();
            SynchronizationContext.SetSynchronizationContext(ctx);

            ctx.Post(o => Console.WriteLine("Hello"), null);

            ctx.Post(
                async o =>
                    {
                        Console.WriteLine("Entered async lambda.");
                        Console.ReadLine();
                        await Console.Out.WriteAsync("Async out.");
                        Console.WriteLine("Async post back.");
                        Console.ReadLine();
                        Console.WriteLine("End async.");
                    }, null);

            ctx.Run();
        }
    }

    public class SyncContext : SynchronizationContext
    {
        private readonly Queue<Action> messageQueue = new Queue<Action>();

        private bool IsRunning { get; set; }

        public void Stop()
        {
            IsRunning = false;
            if (messageQueue.Count == 0)
            {
                lock (messageQueue)
                {
                    Monitor.Pulse(messageQueue);
                }
            }
        }

        public void Run()
        {
            IsRunning = true;
            Action queuedAction;

            while (IsRunning)
            {
                lock (messageQueue)
                {
                    if (messageQueue.Count == 0)
                    {
                        // Make sure it wasn't stopped while contending for the lock.
                        if (IsRunning)
                        {
                            Monitor.Wait(messageQueue);
                        }

                        if (!IsRunning)
                        {
                            break;
                        }
                    }

                    queuedAction = messageQueue.Dequeue();
                }
                queuedAction();
                Console.WriteLine(Process.GetCurrentProcess().Threads.Count);

            }
        }

        /// <summary>
        ///     When overridden in a derived class, dispatches an asynchronous message to a synchronization context.
        /// </summary>
        /// <param name="d">The <see cref="T:System.Threading.SendOrPostCallback" /> delegate to call.</param>
        /// <param name="state">The object passed to the delegate.</param>
        public override void Post(SendOrPostCallback d, object state)
        {
            lock (messageQueue)
            {
                messageQueue.Enqueue(() => d(state));
                Monitor.Pulse(messageQueue);
            }
        }
    }
}

你看到了什么输出?在使用VS2013和.net 4.5的机器上,我得到以下输出:

Hello
12
Entered async lambda.

Async out.Async post back.

End async.
12
在Linqpad中,我得到以下信息:

Hello
28
Entered async lambda.
Async out.Async post back.
End async.
28

因此,在这两种情况下,看起来都没有启动任何新线程。

您看到的行为与同步上下文无关,如下面的代码所示。我认为原因是,当您启动异步任务时,您正在点击TPL任务调度程序

最可能的情况是,任务调度器(或其背后的其他TPL惰性初始化逻辑)根据自己的需要旋转多个线程(或者使用
ThreadPool
threads)。显然,这种逻辑足够聪明,不会对synchronos任务执行此操作,例如,如果您执行了
wait Task.FromResult(true)
而不是
wait Console.Out.WriteAsync()

下面是一个使用自定义等待器的示例:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        static async Task TestAsync()
        {
            Console.WriteLine("A, threads: {0}, thread: {1}", Process.GetCurrentProcess().Threads.Count, Thread.CurrentThread.ManagedThreadId);
            await new CustomAwaiter(async: false);
            Console.WriteLine("B, threads: {0}, thread: {1}", Process.GetCurrentProcess().Threads.Count, Thread.CurrentThread.ManagedThreadId);

            Console.WriteLine("C, threads: {0}, thread: {1}", Process.GetCurrentProcess().Threads.Count, Thread.CurrentThread.ManagedThreadId);
            await new CustomAwaiter(async: true);
            Console.WriteLine("D, threads: {0}, thread: {1}", Process.GetCurrentProcess().Threads.Count, Thread.CurrentThread.ManagedThreadId);
        }

        static void Main(string[] args)
        {
            TestAsync().Wait();
        }
    }

    public struct CustomAwaiter:
        System.Runtime.CompilerServices.INotifyCompletion
    {
        readonly bool _async;

        public CustomAwaiter(bool async = true) { _async = async; }

        // awaiter methods
        public CustomAwaiter GetAwaiter() { return this; }

        public bool IsCompleted {get { return !_async; } }

        public void GetResult() { }

        // ICriticalNotifyCompletion
        public void OnCompleted(Action continuation) { continuation(); }
    }
}
输出:

A, threads: 3, thread: 1 B, threads: 3, thread: 1 C, threads: 3, thread: 1 D, threads: 6, thread: 1 A、 线程:3,线程:1 B、 线程:3,线程:1 C、 线程:3,线程:1 D、 线程:6,线程:1
正如@StephenCleary所指出的,线程的数量确实不是您应该担心的问题。无论如何,你也不应该依赖这种行为。如果您对那里到底发生了什么感到好奇,请使用。

Updated和环境详细信息深入了解第三方物流实施细节。抱歉重复编辑。你确定你没有运行调试构建,或者主机进程没有增加你的线程数吗?我不明白为什么这个简单的控制台应用程序应该运行那么多线程。我在调试中使用它,在发布模式中两次都返回了13个线程。我想,这是一个根据系统而变化的实现细节,正如Stephen Cleary评论的那样。在运行时内可能无法控制它。线程数(例如,线程池中的线程数)是一个实现细节,不需要担心。这里从不使用线程池。那么,当您显式地编程使用单个线程时,有没有办法确保池中没有启动额外的线程?还有,所有这些线程都是线程池线程?有任何文档可以理解这些额外线程是什么吗?没有单线程的.NET应用程序。运行时总是可以将工作排队到线程池,例如控制台I/O。 A, threads: 3, thread: 1 B, threads: 3, thread: 1 C, threads: 3, thread: 1 D, threads: 6, thread: 1