【问题标题】:How are Async/Await methods executed on threads? [duplicate]Async/Await 方法是如何在线程上执行的? [复制]
【发布时间】:2020-03-19 10:57:08
【问题描述】:

我试图了解async/await 在 C# 中的工作原理。我创建了以下示例。

public class SynchronizedCache<TKey, TValue>
{
    private readonly System.Threading.ReaderWriterLockSlim _lock
        = new System.Threading.ReaderWriterLockSlim();
    private readonly IDictionary<TKey, TValue> _dictionary
        = new Dictionary<TKey, TValue>();

    public TValue this[TKey key]
    {
        get
        {
            _lock.EnterReadLock();
            try
            {
                if (!_dictionary.TryGetValue(key, out var value))
                {
                    throw new InvalidOperationException($"key {key} doesn't exist");
                }

                return value;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        set
        {
            _lock.EnterWriteLock();
            _dictionary[key] = value;
            _lock.ExitWriteLock();
        }
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        try
        {
            value = this[key];
            return true;
        }
        catch (Exception)
        {
            value = default(TValue);
            return false;
        }
    }

    public void SetValue(TKey key, TValue value)
    {
        this[key] = value;
    }
}

class Program
{
    private static readonly SynchronizedCache<int, string> Cache
        = new SynchronizedCache<int, string>();
    private static readonly Random Random = new Random();
    private static readonly IList<Task> Readers = new List<Task>();
    private static readonly IList<Task> Writers = new List<Task>();

    static async Task ReaderTask(string name)
    {
        while (true)
        {
            lock (Random)
            {
                var index = Random.Next(1, 100);
                if (Cache.TryGetValue(index, out var value))
                {
                    Console.WriteLine(
                        $"Reader({name} {Thread.CurrentThread.ManagedThreadId}"
                        + $", cache[{index}]={value}");
                }
            }

            await Task.Delay(1000);
        }
    }

    static async Task WriterTask(string name)
    {
        while (true)
        {
            lock (Random)
            {
                var index = Random.Next(1, 100);
                var value = Random.Next(100, 1000);
                Cache[index] = value.ToString();
                Console.WriteLine(
                    $"Writer({name}) {Thread.CurrentThread.ManagedThreadId}"
                    + $", cache[{index}]={Cache[index]}");
            }

            await Task.Delay(500);
        }
    }

    private static async Task Main(string[] args)
    {
        Console.WriteLine($"Main thread {Thread.CurrentThread.ManagedThreadId}");
        await Task.WhenAll(
            // readers
            ReaderTask("Reader 1"),
            ReaderTask("Reader 2"),
            ReaderTask("Reader 3"),
            ReaderTask("Reader 4"),

            // writers
            WriterTask("Writer 1"),
            WriterTask("Writer 2"));
    }
}

当我运行上面的代码时,我得到以下输出:

Main thread 1
Writer(Writer 1) 1, cache[80]=325
Writer(Writer 2) 1, cache[28]=550
Writer(Writer 2) 4, cache[95]=172
Writer(Writer 1) 5, cache[71]=132
Writer(Writer 2) 5, cache[67]=454
Writer(Writer 1) 5, cache[91]=314
Writer(Writer 2) 5, cache[39]=154
Writer(Writer 1) 5, cache[99]=921
Writer(Writer 2) 6, cache[56]=291
Writer(Writer 1) 5, cache[8]=495
Writer(Writer 2) 6, cache[74]=907
Writer(Writer 1) 5, cache[35]=101
Writer(Writer 2) 5, cache[11]=449
Writer(Writer 1) 6, cache[64]=932
Writer(Writer 2) 7, cache[34]=825
Writer(Writer 1) 5, cache[88]=869
Reader(Reader 1 4, cache[8]=495
Writer(Writer 2) 8, cache[43]=983
Writer(Writer 1) 6, cache[15]=590
Writer(Writer 2) 5, cache[22]=276
Writer(Writer 1) 6, cache[58]=845

我知道我的Main() 线程与ThreadId 1 在一起,但我不明白其他线程在哪里创建?我没有在这里创建任何特定的线程。调用异步函数是否会创建已在线程池线程上调度的任务?

【问题讨论】:

  • ReadWriter 和 C# 中的大多数锁不能用于同步任务。您应该寻找自定义实现。 SemaphoreSlim 可用于任务。任务只是一个工作单元,不是线程,不应该被视为一个,它们不并行运行,它们可以在单个 UI 线程上运行,它更像是游戏中的协程。
  • @eocron “任务只是不是线程的工作单元,..”这正是我的理解。但是当我查看控制台输出时,我对到底发生了什么感到困惑。
  • Microsoft Docs 上的文章 Async In Depth 让您了解会发生什么(特别是在“深入了解任务和任务 以实现 CPU 绑定操作”部分)
  • 我强烈推荐阅读There is No Thread - Stephen Cleary
  • Stack Overflow 上已经有大量讨论,详细解释了它是如何工作的。少数见标记重复。短版:await 必须返回执行somewhere。在控制台程序中,“某处”是线程池中的线程。因此线程 ID 是多种多样的。在其他情况下,例如 Winforms,除非另有说明,否则恢复执行的上下文是主 UI 线程,因此在这种情况下您会看到相同的线程 ID。

标签: c# async-await


【解决方案1】:

这是对async 的滥用。在示例中,您的任务是计算绑定的,异步执行不会给您带来任何好处。

除此之外,将方法标记为async 或为此创建一个新的Task 并不能保证会启动一个新线程来为它服务。目前只有 TaskCreationOptions.LongRunning 在提供时使用新线程,但这只是一个实现细节,如有更改,恕不另行通知。

当调用真正的异步方法时,例如从网络或文件中读取,该方法会一直执行到第一个无法同步执行的await。使用环境SynchronizationContext 在该点排队继续。当等待的操作完成时,如果使用了ConfigureAwait(false) 或没有环境上下文,则启动可能在ThreadPool 上完成的延续。没有一个新线程可以保证在所有这些中执行。

【讨论】:

  • "这是对异步的滥用。在示例中,您的任务是计算绑定的,异步执行不会给您带来任何好处。"我知道这不是最好的例子,但是无论学习这些概念需要什么。我创建了这个例子来了解这些东西是如何工作的。最佳实践完全是一个单独的主题,我完全同意你的看法。
【解决方案2】:

ID 为 4、5、6、7 和 8 的线程由 ThreadPool 类创建,并在每次完成 Task 时使用,以运行此 Task 的注册延续。这些线程的数量可以通过查询ThreadPool.ThreadCount属性随时获取:

获取当前存在的线程池线程数。

最初ThreadPool 类创建的线程数与机器的核心数一样多。如果ThreadPool 处于饥饿状态,它会以每 500 毫秒一个的速率创建新线程。可以通过调用ThreadPool.SetMinThreads方法来控制线程池线程的最小数量。

线程池线程针对细粒度工作负载进行了优化。单个线程池线程每秒可以运行数百万个Task 延续。它还可以运行持续数秒或数分钟的冗长工作负载,但通常应避免长时间使用线程池线程,以防止 ThreadPool 饿死。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多