在Task.WhenAll中执行许多任务时的C#线程

时间:2017-01-31 08:17:32

标签: c# async-await

如果在单个线程上执行此操作会发生什么:

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...
}

并说items中有10,000个项目。当等待之后每个SomeAsyncMethod继续时,它就会从线程池中的一个线程上继续。因此,当许多SomeAsyncsMethod s返回时,线程池中的多个线程将同时被占用,或者只有一个线程执行"做一些CPU密集型的东西"在这种情况下,SomeAsyncMethod在任何特定时刻?

更新:好的,这是一个示例程序。当我在具有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);
            }
        }
    }
}

1 个答案:

答案 0 :(得分:3)

最可能的情况是&#34; CPU密集的东西&#34;每个都会发生在一个随机的线程池线程中 - 如果它真的受CPU限制,那么每个逻辑核心就可以完成大约1-2个线程。

关键是,虽然原始任务的继续(Task.WhenAll)将在UI线程上运行(如果 同步上下文,当然),继续由于您明确请求忽略同步上下文(ConfigureAwait(false)),因此将在线程池中发布单个I / O操作。

但是,如果I / O请求同步完成,那么所有内容都有可能在原始线程上运行。在这种情况下,不会进行异步调度,并且任务没有机会切换线程。如果您需要确保并行化,则必须明确使用Task.Run

还应该注意,这主要取决于实现,而不是你可以依赖的东西。对于大量异步I / O应用程序来说,它可能也是一种糟糕的方法,因为你可能在I / O线程池的线程上运行CPU密集型的东西 - 扰乱了线程池中线程的框架平衡,并阻止新的异步响应通过,直到您完成工作。如果你正在做的工作不是纯粹的CPU工作 - 尤其如此 - 例如,在某个类似Web服务器的事情上,阻塞线程池线程会非常痛苦。