簡體   English   中英

在Task.WhenAll中執行許多任務時的C#線程

[英]C# threads when executing many tasks in Task.WhenAll

如果在一個線程上我這樣做會發生什么:

await Task.WhenAll(items.select(x => SomeAsyncMethod(x)))

// Where SomeAsyncMethod is defined like this (writeAsync is pure async io)
async Task SomeAsyncMethod(Item item){
  await myDevice.writeAsync(...).ConfigureAwait(false);
  //do some cpu intensive stuff...
}

並說項目中有10,000件items 當每個SomeAsyncMethod在await之后繼續時,它就會在線程池的一個線程上執行。 因此,當許多SomeAsyncsMethod返回時,線程池中的多個線程將同時被占用,或者在這種情況下,在任何給定時刻, SomeAsyncMethod中只有一個線程執行“做一些CPU密集型事情”嗎?

更新:好的,這是一個示例程序。 當我在具有8個邏輯核心的PC上進行測試時,minthreads為12或13,maxthreads在35-40范圍內結束。 因此看起來好像最多可以創建4個線程。 創建10.000或100.000文件並不重要 - 使用相同的最大線程數 - 這可能是因為所有任務都排隊等待訪問文件系統? 請注意,該程序將在c:\\ tmp \\ asynctest中創建大量小文件:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication4 {
    internal class Program {
        private static void Main(string[] args) {
            var myDevice = new MyDevice();
            var ints = new List<int>();
            for (var i = 0; i < 10000; i++) {
                ints.Add(i);
            }
            var task = Task.WhenAll(ints.Select(i => myDevice.WriteTextAsync(i.ToString())));
            task.Wait();
            Console.WriteLine("Max thread count = " + myDevice.MaxThreadCount);
            Console.WriteLine("Min thread count = " + myDevice.MinThreadCount);
            Console.ReadLine();
        }
    }

    public class MyDevice {
        public ConcurrentDictionary<string, string> ThreadIds;
        public int MaxThreadCount;
        public int MinThreadCount = Process.GetCurrentProcess().Threads.Count;
        public async Task WriteTextAsync(string text) {
            var filePath = @"c:\tmp\asynctest\" + text + ".txt";
            var encodedText = Encoding.Unicode.GetBytes(text);
            using (var sourceStream = new FileStream(filePath,
                FileMode.Append, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) {
                await sourceStream.WriteAsync(encodedText, 0, encodedText.Length).ConfigureAwait(false);
                MaxThreadCount = Math.Max(MaxThreadCount, Process.GetCurrentProcess().Threads.Count);
                MinThreadCount = Math.Min(MinThreadCount, Process.GetCurrentProcess().Threads.Count);
            }
        }
    }
}

更新2.現在,如果我啟動多個線程,每個線程同時執行大量的aysnc io任務,那么與更新1中的單線程示例相比,它看起來不像總共使用更多線程。在測試中我剛剛運行,其中每個由4個線程創建10.000個文件,然后最大線程數為41,最小線程數為12 - 因此似乎有一些中心控制用於異步任務延續的線程數。 這是一個示例,其中4個線程各自啟動10.000異步操作:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication4 {
    internal class Program {
        private static void Main(string[] args) {
            var myDevice = new MyDevice();
            var ints = new List<int>();
            const int limit = 10000;
            for (var i = 0; i < limit; i++) {
                ints.Add(i);
            }

            List<Task> jobs = new List<Task>();
            for (var j = 0; j < 4*limit; j+=limit) {
                var jobid = j;
                jobs.Add(Task.Run(() => Runjob(ints, myDevice, jobid)));
            }
            Task.WaitAll(jobs.ToArray());

            Console.WriteLine("Max thread count = " + myDevice.MaxThreadCount);
            Console.WriteLine("Min thread count = " + myDevice.MinThreadCount);
            Console.ReadLine();
        }

        private static void Runjob(List<int> ints, MyDevice myDevice, int jobid) {
            Console.WriteLine("Starting job " + jobid);
            var task = Task.WhenAll(ints.Select(i => myDevice.WriteTextAsync((jobid+i).ToString())));
            task.Wait();
            Console.WriteLine("Finished job " + jobid);
        }
    }

    public class MyDevice {
        public int MaxThreadCount;
        public int MinThreadCount = Process.GetCurrentProcess().Threads.Count;
        public async Task WriteTextAsync(string text) {
            var filePath = @"c:\tmp\asynctest\" + text + ".txt";
            var encodedText = Encoding.Unicode.GetBytes(text);
            using (var sourceStream = new FileStream(filePath,
                FileMode.Append, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) {
                await sourceStream.WriteAsync(encodedText, 0, encodedText.Length).ConfigureAwait(false);
                MaxThreadCount = Math.Max(MaxThreadCount, Process.GetCurrentProcess().Threads.Count);
                MinThreadCount = Math.Min(MinThreadCount, Process.GetCurrentProcess().Threads.Count);
            }
        }
    }
}

最可能的情況是,“CPU instensive stuff”將在隨機線程池線程中發生 - 如果它真正受CPU限制,那么每個邏輯核心完成工作將獲得大約1-2個線程。

關鍵的一點是,雖然延續原來的任務( Task.WhenAll )會跑回來在UI線程(如果一個同步的情況下,當然),將延續到各個I / O操作將在發布線程池,因為您明確請求忽略同步上下文( ConfigureAwait(false) )。

但是,如果I / O請求同步完成,則所有內容都有可能在原始線程上運行。 在這種情況下,不會進行異步調度,並且任務沒有機會切換線程。 如果需要確保並行化,則必須明確使用Task.Run

還應該注意,這主要取決於實現,而不是你可以依賴的東西。 對於大量異步I / O應用程序來說,這可能也是一種糟糕的方法,因為您可能在I / O線程池的線程上運行CPU密集型內容 - 這會擾亂線程池中線程的框架平衡,並防止新的異步響應通過,直到你完成你的工作。 如果您正在進行的工作不是純粹的CPU工作,那么尤其如此 - 例如,在線程池線程上阻塞對於像Web服務器這樣的事情來說可能非常痛苦。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM