使用锁定

时间:2017-05-08 18:42:56

标签: c# asynchronous

我注意到我的进程中的初始减速,并且在进行多次挂起后,我能够隔离问题并使用以下代码重现场景。我正在使用一个具有锁定而不具有锁定的库,这最终会调用某些方法的用户端实现。这些方法使用httpclient进行异步调用。这些异步调用是从库内的这些锁中进行的。

现在,关于发生了什么的理论(如果我错了,请纠正我): 获得spun的任务尝试获取锁并保持足够快的线程,以便第一个PingAsync方法需要等待默认任务调度程序启动一个新线程以使其运行,这是0.5秒,基于默认的.net调度算法。这就是为什么我认为我发现总任务的延迟大于32,这也随着总任务数的增加而线性增加。

解决方法:

  1. 增加minthreads计数,我认为这是治疗症状,而不是实际问题。
  2. 另一种方法是使用有限的并发来控制触发的任务数。但是这些是由网络服务器为传入的httprequests转换的任务,通常我们无法控制它(或者我们会这样做吗?)
  3. 我知道结合asyc和非async是糟糕的设计和使用sempahores'异步调用将是一个更好的方法。假设我无法控制这个库,那么如何解决这个问题?

        const int ParallelCount = 16;
        const int TotalTasks = 33;
    
        static object _lockObj = new object();
        static HttpClient _httpClient = new HttpClient();
        static int count = 0;
    
        static void Main(string[] args)
        {
            ThreadPool.GetMinThreads(out int workerThreads, out int ioThreads);
            Console.WriteLine($"Min threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
    
            ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
            Console.WriteLine($"Max threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
    
            //var done = ThreadPool.SetMaxThreads(1024, 1000);
            //ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
            //Console.WriteLine($"Set Max Threads success? {done}.");
            //Console.WriteLine($"Max threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
    
            //var done = ThreadPool.SetMinThreads(1024, 1000);
            //ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
            //Console.WriteLine($"Set Min Threads success? {done}.");
            //Console.WriteLine($"Min threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
    
            var startTime = DateTime.UtcNow;
            var tasks = new List<Task>();
    
            for (int i = 0; i < TotalTasks; i++)
            {
                tasks.Add(Task.Run(() => LibraryMethod()));
    
                //while (tasks.Count > ParallelCount)
                //{
                //    var task = Task.WhenAny(tasks.ToArray()).GetAwaiter().GetResult();
    
                //    if (task.IsFaulted)
                //    {
                //        throw task.Exception;
                //    }
    
                //    tasks.Remove(task);
                //}
            }
    
            Task.WaitAll(tasks.ToArray());
    
            //while (tasks.Count > 0)
            //{
            //    var task = Task.WhenAny(tasks.ToArray()).GetAwaiter().GetResult();
    
            //    if (task.IsFaulted)
            //    {
            //        throw task.Exception;
            //    }
    
            //    tasks.Remove(task);
    
            //    Console.Write(".");
            //}
    
            Console.Write($"\nDone in {(DateTime.UtcNow-startTime).TotalMilliseconds}");
            Console.ReadLine();
        }
    

    假设这是调用库方法的部分,

        public static void LibraryMethod()
        {
            lock (_lockObj)
            {
                SimpleNonAsync();
            }
        }
    

    最终,调用此方法的用户实现,这是异步。

        public static void SimpleNonAsync()
        {
                //PingAsync().Result;
                //PingAsync().ConfigureAwaiter(false).Wait();
                PingAsync().Wait();
        }
    
        private static async Task PingAsync()
        {
            Console.Write($"{Interlocked.Increment(ref count)}.");
    
            await _httpClient.SendAsync(new HttpRequestMessage
            {
                RequestUri = new Uri($@"http://127.0.0.1"),
                Method = HttpMethod.Get
            });
        }
    

1 个答案:

答案 0 :(得分:2)

  

这些异步调用是从库内的这些锁中进行的。

这是一个设计缺陷。在锁定时,没有人应该调用任意代码。

那就是说,锁与你所看到的问题无关。

  

我知道结合asyc和非async是糟糕的设计和使用sempahores&#39;异步调用将是一个更好的方法。假设我无法控制这个库,那么如何解决这个问题?

问题 表示库正在强制您的代码同步。这意味着每次下载都会阻止一个线程;只要图书馆的回调是同步的,就没有办法解决这个问题。

  

增加minthreads计数,我认为这是治疗症状,而不是实际问题。

如果您无法修改库,那么必须每个请求使用一个线程,这就成了一种可行的解决方法。您来处理症状,因为您无法解决问题(即图书馆)。

  

另一种方法是使用有限的并发来控制触发的任务数。但这些是由网络服务器为传入的httprequests转换的任务,通常我们无法控制它(或者我们会吗?)

没有;导致问题的任务是您使用Task.Run自行调整的问题。服务器上的任务完全独立;您的代码无法 影响甚至检测它们。

如果你想要更高的并发性而不等待线程注入,那么你需要增加最小线程,你也可能需要增加ServicePointManager.DefaultConnectionLimit。然后,您可以继续使用Task.Run或(我希望)Parallel或并行LINQ进行并行处理。 Parallel / Parallel LINQ的一个不错的方面是它内置了对限制的支持,如果还需要的话。