如何在线程上执行Async / Await方法?

时间:2019-11-23 16:53:32

标签: c# async-await

我试图了解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一起使用,但是我不知道其他线程是在哪里创建的?我不在这里创建任何特定的线程。调用异步函数是否会创建已在线程池线程上调度的任务?

2 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:-1)

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

  

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

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

线程池线程针对粒度工作负载进行了优化。一个线程池线程每秒可以运行数百万个ThreadPool连续。它也可以运行很长的工作量,持续数秒或数分钟,但是通常您应该避免使用线程池线程那么长时间,以防止Task饿死。